Prompt sounds for ESP32 AudioKit (ES8388 V1/V2) with Bluetooth A2DP source #2018
-
Hello, I'm trying to make a Bluetooth speaker using ESP32-A2DP library (from the same author, latest) based on the example code given, and I want to use Audio Tools library to implement playing back prompt sounds based on the state A2DP reports. However I'm having trouble understanding the required code. This code attached is currently non-functional and admittedly amateurish, but I hope it helps understanding what I'm trying to do. // Placeholder functions are used. If something doesn't actually exist in the library,
// think of it as "actual code goes here later" kind of statement. Any line marked with
// commented-out "P" means that line is intended to have them.
// #include <vorbis-tremor.h>
#include <CodecVorbis.h>
#include <OneButton.h>
#include "AudioTools.h"
#include "AudioTools/AudioLibs/AudioBoardStream.h" // install https://github.com/pschatzmann/arduino-audio-driver
#include "AudioTools/Disk/AudioSourceSPIFFS.h"
#include "BluetoothA2DPSink.h"
#include "FS.h" //P
#include "esp_spiffs.h" //P
#define KEY_VOLUP 18
#define KEY_NEXT 23
#define KEY_PLAY 19
#define KEY_PREV 13
#define KEY_VOLDN 36
// Filesystem definitions (Do not mix with below)
// Currently uses OGG but can be changed to WAV if resampling can be performed
#define FS_SND_VOL_MIN "vol-min.ogg"
#define FS_SND_VOL_MAX "vol-max.ogg"
#define FS_SND_BAT_LOW "battlow.ogg"
#define FS_SND_STA_CONNECT "connected.ogg"
#define FS_SND_STA_DISCONNECT "disconnected.ogg"
#define FS_SND_PWR_ON "poweron.ogg"
#define FS_SND_PWR_OFF "poweroff.ogg"
#define FS_SND_STA_LINK_LOSS "linkloss.ogg"
#define FS_SND_ERASE_PAIRED_DEVICES "init.ogg"
#define FS_SND_PAIR_ERROR "pair-fail.ogg"
// Index-only definitions (Do not mix with above)
#define SND_VOL_MIN 6
#define SND_VOL_MAX 7
#define SND_BAT_LOW 8
#define SND_STA_CONNECT 2
#define SND_STA_DISCONNECT 3
#define SND_PWR_ON 0
#define SND_PWR_OFF 1
#define SND_STA_LINK_LOSS 5
#define SND_ERASE_PAIRED_DEVICES 9
#define SND_PAIR_ERROR 4
int curVolume = 64;
bool isMuted = false;
uint16_t battTick = 0;
uint16_t battCheckTick = 3000;
AudioBoardStream kit(AudioKitEs8388V1);
BluetoothA2DPSink a2dp_sink(kit);
AudioGeneratorOGG *snd; //P
AudioFileSourceSPIFFS *file; //P
AudioOutputI2S *out; //P
OneButton volup_btn(KEY_VOLUP, true);
OneButton next_btn(KEY_NEXT, true);
OneButton play_btn(KEY_PLAY, true);
OneButton prev_btn(KEY_PREV, true);
OneButton voldn_btn(KEY_VOLDN, true);
void setup() {
//LOGLEVEL_AUDIOKIT = AudioKitInfo;
Serial.begin(115200);
if (!SPIFFS.begin(true)) {
Serial.println("Error mounting SPIFFS volume");
return;
}
// use medium volume
kit.setVolume(1.0);
// Enable below if the speaker's volume behavior is not acceptable to force it into a known state
// a2dp_sink.set_volume(curVolume);
// Setup buttons
volup_btn.attachClick(volumeUpFine);
volup_btn.attachDuringLongPress(volumeUpCoarse);
next_btn.attachClick(goNext);
play_btn.attachClick(goPlay);
prev_btn.attachClick(goPrev);
voldn_btn.attachClick(volumeDownFine);
voldn_btn.attachDuringLongPress(volumeDownCoarse);
// Set delays
volup_btn.setDebounceMs(100);
next_btn.setDebounceMs(100);
play_btn.setDebounceMs(100);
prev_btn.setDebounceMs(100);
voldn_btn.setDebounceMs(100);
volup_btn.setClickMs(500);
next_btn.setClickMs(500);
play_btn.setClickMs(500);
prev_btn.setClickMs(500);
voldn_btn.setClickMs(500);
volup_btn.setPressMs(1000);
next_btn.setPressMs(1000);
play_btn.setPressMs(1000);
prev_btn.setPressMs(1000);
voldn_btn.setPressMs(1000);
// start a2dp
a2dp_sink.start("ESP-BT-SPK");
playSound(FS_SND_PWR_ON);
}
void volumeUpFine() {
curVolume = a2dp_sink.get_volume();
curVolume = curVolume + 5;
if (curVolume > 127) {
curVolume = 127;
playSound(FS_SND_VOL_MAX);
}
a2dp_sink.set_volume(curVolume);
}
void volumeUpCoarse() {
curVolume = a2dp_sink.get_volume();
curVolume = curVolume + 10;
if (curVolume > 127) {
curVolume = 127;
playSound(FS_SND_VOL_MAX);
}
a2dp_sink.set_volume(curVolume);
}
void volumeDownFine() {
curVolume = a2dp_sink.get_volume();
curVolume = curVolume - 5;
if (curVolume < 0) {
curVolume = 0;
playSound(FS_SND_VOL_MIN);
}
a2dp_sink.set_volume(curVolume);
}
void volumeDownCoarse() {
curVolume = a2dp_sink.get_volume();
curVolume = curVolume - 10;
if (curVolume < 0) {
curVolume = 0;
playSound(FS_SND_VOL_MIN);
}
a2dp_sink.set_volume(curVolume);
}
void playSound(const char* filename) {
// TODO: Implement SPIFFS/C Header WAV playback
// Need to switch between WAV source and A2DP audio source with upsampling if needed
// Or can use OGG Vorbis... I guess?
// [Prompt Sound Output]-----(I2S Stream 2)----.
// |
// [Bluetooth AVRCP]---->(I2S Stream 1)---->[I2S Switcher]---->[I2S Output]---->[I2S DAC]
// Stop playing previous sound if a sound is already playing
if (snd && snd->isRunning()) { //P
snd->stop(); //P
delete snd; //P
delete file; //P
delete out; //P
}
file = new AudioFileSourceSPIFFS(filename); //P
out = new AudioOutputI2S(); //P
out->SetGain(0.5); //P, Prompt tone volume
snd = new AudioGeneratorOGG(); //P
snd->begin(file, out); //P
}
void goNext() {
a2dp_sink.next();
}
void goPlay() {
a2dp_sink.play();
}
void goPrev() {
a2dp_sink.previous();
}
void mute() {
if (isMuted) {
a2dp_sink.set_volume(curVolume);
}
else {
a2dp_sink.set_volume(0);
}
isMuted = !isMuted;
}
int checkBatteryLevel() {
float threshold = 3.53;
// ADC reading code goes here...
if (result < threshold) {
playSound(FS_SND_BAT_LOW);
}
}
void loop() {
delay(10);
volup_btn.tick();
next_btn.tick();
play_btn.tick();
prev_btn.tick();
voldn_btn.tick();
battTick++;
if (battTick >= battCheckTick) {
checkBatteryLevel();
battTick = 0;
}
} |
Beta Was this translation helpful? Give feedback.
Replies: 7 comments 19 replies
-
Please read the chapter about Codecs: instead of doing coper.copy() in the loop you can just call copier.copyAll() to output the whole file. Just make sure that your playback is not changing the sample rate or channels: otherwise you need to restore it to the original values! |
Beta Was this translation helpful? Give feedback.
-
You can consider I2S as a blocking queue: If you write data to it you just add more data to the end of the queue. Each copy step has 1kbytes (by default) which is only 256 stereo audio samples. At 44100 samples per second this is only playing for 5.8 milliseconds! If you don't provide the data fast enough, the sound is breaking up at stuttering... |
Beta Was this translation helpful? Give feedback.
-
That depends on your input stream:
|
Beta Was this translation helpful? Give feedback.
-
Now the problem is an approach to playing multiple sounds, one at a time, on demand. These are my best interpretations of the library, does any of it sound plausible?
Plus, does A2DPStream support getting the status (connection status to be more exact) from the connection, or is it supposed to be used with |
Beta Was this translation helpful? Give feedback.
-
Before deciding on a decoder and approach, check how much RAM you have left when A2DP playback is active and measure how much RAM your decoder is using at playback! |
Beta Was this translation helpful? Give feedback.
-
I think I am starting to slowly understand what you meant, now that I combed through hundreds and thousands of lines of code. I sincerely apologize for any inconvenience. This is what I have currently understood:
Now all that remains is to get an answer to this question: Currently, OneButton, AudioTools, AudioBoardStream, CodecVorbis, BluetoothA2DPSink, and the sounds are being used, with BluetoothA2DPSink being used because I can't find a way to get the callback events and A2DP commands from A2DPStream. (If I find a way to use callback events on A2DPStream, I will change over to that part of the library) |
Beta Was this translation helpful? Give feedback.
-
It is quite clear to me that I won't get help through this way. Since there is not an example code for relevant part of the library, I am unable to continue. Have a nice day. |
Beta Was this translation helpful? Give feedback.
Please read the chapter about Codecs: instead of doing coper.copy() in the loop you can just call copier.copyAll() to output the whole file.
I also suggest to store the data in PROGMEM and using a MemoryStream as described in this Wiki instead of using SPIFFS.
Just make sure that your playback is not changing the sample rate or channels: otherwise you need to restore it to the original values!