A2DP and AudioPlayer do not mix well #2158
-
Problem DescriptionFollow-up from #2151 When mixing a CallbackBuffer'ed A2DP stream with an AudioPlayer instance, there appear to be some quirks with how AudioPlayer is coded that cause things to not function properly. For context, my testing script is designed to play a short drum sound over I2S when a button is pressed. It also acts as an A2DP sink, and if A2DP is active, it will mix the drum sound with the A2DP stream before sending the mixed audio to I2S. The mixing actually works perfectly while the sample is playing, but for a moment after the sample is finished, the A2DP stream stutters before returning to normal. I traced the issue back to the AudioPlayer and I was able to figure out a band-aid "fix" by making some changes to the size_t copy(size_t bytes, bool a2dp_stream_fix = false) {
size_t result = 0;
if (active) {
...
// return silence when there was no data
if (result < bytes && silence_on_inactive) {
//LOGW("Ran out of file bytes, only got %u, so padding with %u bytes of silence", result, (bytes - result));
writeSilence(bytes - result);
if (a2dp_stream_fix) setActive(false); // Set inactive immediately if mixing with A2DP
}
} else {
// e.g. A2DP should still receive data to keep the connection open
if (silence_on_inactive) {
//LOGW("Player not active, writing %u bytes of silence", bytes);
writeSilence(bytes);
}
}
return result;
} Normally, the function is set to return silent bytes whenever This didn't address the stuttering after the audio finished playing. I was able to figure out that, for reasons unknown to me, the AudioPlayer remains Active for a bit ever after the player reached the end of the audio file. Now, the function does just return silence in this case too, and it seems to even pad the correct amount of silence if, for example, 2000 bytes are requested but there are only 500 bytes of audio left. However, even though the correct amount of silent bytes appear to be returned here, it still causes the mixer output to stutter until the player goes Inactive. So, I just brute-forced this by setting it to inactive as soon as there are fewer bytes left in the file than requested to copy, which works, except that it breaks playback when not mixing the AudioPlayer with A2DP. So, I added an argument to the function so that I can selectively make the player immediately inactive only when A2DP is streaming. This works, but is definitely a hack and not the right way to go about this. In addition, Hopefully this is enough info to go off of. I'm also including the .wav file I'm using, in case there's something special about it that results in the issues I'm having. Thanks!! Device DescriptionESP32-Wroom dev board, PCM5102A DAC Sketch#include <BluetoothA2DPSink.h>
#include <AudioTools.h>
#include <AudioTools/AudioCodecs/CodecWAV.h> // WAV decoder
#include <AudioTools/Disk/AudioSourceLittleFS.h>
// — Config —
constexpr char *startFilePath="/";
constexpr char* ext=".wav";
constexpr int TRIGGER_PIN = 21;
bool lastTriggerState = HIGH;
esp_a2d_audio_state_t bluetoothAudioState = ESP_A2D_AUDIO_STATE_SUSPEND;
BluetoothA2DPSink a2dp_sink;
I2SStream i2s;
OutputMixer<int16_t> mixer(i2s, 2);
CallbackStream cb;
BufferedStream buffered(cb);
AudioSourceLittleFS source(startFilePath, ext);
WAVDecoder wavDecoder;
AudioPlayer player(source, mixer, wavDecoder);
AudioInfo fmt{44100, 2, 16};
// A2DP callback: send A2DP frames into callback buffer
void onA2dpData(const uint8_t* data, uint32_t len) {
buffered.write(data, len);
}
// Callback to change AudioPlayer output depending on whether A2DP is active or not.
// If A2DP is active, output to Mixer, otherwise output directly to I2S
void audio_state_changed(esp_a2d_audio_state_t state, void *ptr){
Serial.println(a2dp_sink.to_str(state));
bluetoothAudioState = state;
if (state == ESP_A2D_AUDIO_STATE_STARTED) {
player.setOutput(mixer);
} else {
player.setOutput(i2s);
}
}
// with each A2DP write: write X bytes of A2DP data to Mixer index 0, and copy X
// bytes of AudioPlayer data to Mixer index 1. Then flush Mixer buffer to I2S
size_t onWrite(const uint8_t *data, size_t len){
//LOGW("=> onWrite: %u", len);
mixer.write(0, data, len);
mixer.setIndex(1);
player.copy(len, true); // Call with custom A2DP fix
mixer.flushMixer();
return len;
}
void setup() {
Serial.begin(115200);
//AudioToolsLogger.begin(Serial, AudioToolsLogLevel::Info);
pinMode(TRIGGER_PIN, INPUT_PULLUP);
// A2DP sink config
a2dp_sink.set_stream_reader(onA2dpData, false);
a2dp_sink.set_auto_reconnect(true);
a2dp_sink.set_on_audio_state_changed(audio_state_changed);
a2dp_sink.start("a2dp-i2s");
auto cfg = i2s.defaultConfig(TX_MODE);
cfg.sample_rate = 44100;
cfg.bits_per_sample = fmt.bits_per_sample;
cfg.channels = fmt.channels;
cfg.pin_bck = 18;
cfg.pin_ws = 22;
cfg.pin_data = 19;
i2s.begin(cfg);
mixer.setAutoIndex(false);
mixer.begin();
player.setSilenceOnInactive(true);
player.setAutoFade(false);
player.begin(0, false);
player.setAutoNext(false);
cb.setWriteCallback(onWrite);
}
void loop() {
// Only copy in main loop when A2DP is NOT streaming
if (bluetoothAudioState != ESP_A2D_AUDIO_STATE_STARTED) player.copy();
// --- GPIO trigger check ---
bool triggerState = digitalRead(TRIGGER_PIN);
if (lastTriggerState == HIGH && triggerState == LOW) {
Serial.println("Trigger: Playing sample once");
player.setIndex(0); // Set to replay the same sample
player.play();
}
lastTriggerState = triggerState;
delay(5);
} Other Steps to ReproduceNo response What is your development environment (incl. core version info)Arduino IDE 2.3.4 I have checked existing issues, discussions and online documentation
|
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 2 replies
-
Stupid question: why would you use the AudioPlayer at all and not e.g. a much simpler FileLoop or even a MemoryStream ? If you really want to play multiple files in sequence, did you try to set the timout for moving to the next file to 0 ps. Don't assume that you need to write the same amunt on index 1 like on index 0: but query the system for the filled buffer size of index 0 and index 1 and just provide the difference, to make sure that if there was a difference, things are equalling out... |
Beta Was this translation helpful? Give feedback.
Stupid question: why would you use the AudioPlayer at all and not e.g. a much simpler FileLoop or even a MemoryStream ?
If you ignore the wav header you can read from from the wav file directly.
If you really want to play multiple files in sequence, did you try to set the timout for moving to the next file to 0
ps. Don't assume that you need to write the same amunt on index 1 like on index 0: but query the system for the filled buffer size of index 0 and index 1 and just provide the difference, to make sure that if there was a difference, things are equalling out...