Add your own audio processing

Open main\bt_app_av.c

Replace the function void bt_app_a2d_data_cb(const uint8_t *data, uint32_t len) with the following code:

void bt_app_a2d_data_cb(const uint8_t *data, uint32_t len)
{
size_t bytes_written;
uint8_t *mydat = malloc(len);
if(mydat == NULL)
{
ESP_LOGI(BT_AV_TAG, "Not enough memory available");
}
else
{
for (uint32_t i = 0; i < len; i+=4)
{
sample_l_int = (int16_t)((*(data + i + 1) << 8) | *(data + i));
sample_r_int = (int16_t)((*(data + i + 3) << 8) | *(data + i +2));
sample_l_float = (float)sample_l_int / 0x8000;
sample_r_float = (float)sample_r_float / 0x8000;
in = (sample_l_float + sample_r_float) / 2.0f;
out = in * (0.9869865272227986f) + biquad1_z1;
biquad1_z1 = in * (-1.9739730544455971f) + biquad1_z2 - (-1.9738037472862642f) * out;
biquad1_z2 = in * (0.9869865272227986f) - (0.9741423616049301f) * out;
out2 = out * (1.5066355805385152f) + biquad5_z1;
biquad5_z1 = out * ( -0.12972459720746804f) + biquad5_z2 - (-0.12972459720746804f) * out2;
biquad5_z2 = out * (-0.5247301530319133f) - (-0.018094572493397725f) * out2;
out = out2 * 0.4f;
int32_t a;
a = floor(32768 * out);
if (a>32767) {a = 32767;}
if (a<-32768) {a = -32768;}
sample_l_int = (int16_t)a;
*(mydat + i + 1) = (uint8_t) ((sample_l_int >> 8) & 0xFF);
*(mydat + i) = (uint8_t) (0xFF & sample_l_int);
out = in * (0.993448957683901f) + biquad3_z1;
biquad3_z1 = in * (-1.986897915367802f) + biquad3_z2 - (-1.9868727071697347f) * out;
biquad3_z2 = in * (0.993448957683901f) - (0.9869231235658689f) * out;
out2 = out * ( 0.9972686246697485f) + biquad4_z1;
biquad4_z1 = out * (-1.994537249339497f) + biquad4_z2 - (-1.9945119442195687f) * out2;
biquad4_z2 = out * (0.9972686246697485f) - (0.9945625544594253f) * out2;
out = out2 * (0.00008465357966651423f) + biquad2_z1;
biquad2_z1 = out2 * (0.00016930715933302846f) + biquad2_z2 - (-1.9738037472862642f) * out;
biquad2_z2 = out2 * (0.00008465357966651423f) - (0.9741423616049301f) * out;
out = out * 1.0f;
a = floor(32768 * out);
if (a>32767) {a = 32767;}
if (a<-32768) {a = -32768;}
sample_r_int = (int16_t)a;
*(mydat + i + 3) = (uint8_t) ((sample_r_int >> 8) & 0xFF);
*(mydat + i + 2) = (uint8_t) (0xFF & sample_r_int);
}
i2s_write(0, mydat, len, &bytes_written, portMAX_DELAY);
free (mydat);
}
if (++s_pkt_cnt % 100 == 0) {
ESP_LOGI(BT_AV_TAG, "Audio packet count %u", s_pkt_cnt);
ESP_LOGI(BT_AV_TAG, "Process task core: %u\n", xPortGetCoreID());
}
}

Furthermore you need these additional global variables:

static int16_t sample_l_int = 0;
static int16_t sample_r_int = 0;
static float sample_l_float = 0.0f;
static float sample_r_float = 0.0f;
static float in = 0.0f;
static float out = 0.0f;
static float out2 = 0.0f;
static float biquad1_z1 = 0.0f;
static float biquad1_z2 = 0.0f;
static float biquad2_z1 = 0.0f;
static float biquad2_z2 = 0.0f;
static float biquad3_z1 = 0.0f;
static float biquad3_z2 = 0.0f;
static float biquad4_z1 = 0.0f;
static float biquad4_z2 = 0.0f;
static float biquad5_z1 = 0.0f;
static float biquad5_z2 = 0.0f;

You can change the coefficients of the biquad filters to design them as you like. Here is a link to a nice coefficient-generator: Earlevel engineering Biquad Calculator V2

You can also add more biquad-filters or even add your own algorythms. I did not have the time yet to make the code more easy to add/edit the filters. And I still have to test how many biquads filter calculations the ESP32 can handle. Maybe it is even possible to make use of the second core for the audio processing....