Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IOS poor quality audio #533

Open
1 task done
Leo-Ha opened this issue Apr 4, 2024 · 59 comments
Open
1 task done

IOS poor quality audio #533

Leo-Ha opened this issue Apr 4, 2024 · 59 comments

Comments

@Leo-Ha
Copy link

Leo-Ha commented Apr 4, 2024

Problem Description

No matter how i play with the settings, it seems i only get very poor audio quality when i stream audio from IOS device through A2DP, with android it works flawlessly. Is there a solution for this? I checked all realted issues and searched online but did not find a solutrion myself

Device Description

ESP32-WROOM and Iphone 12
External DAC PCM5102A

Sketch

#include "BluetoothA2DPSinkQueued.h"

BluetoothA2DPSinkQueued a2dp_sink;

void setup() {
  a2dp_sink.start("MyMusicQueued");  

}


void loop() {
  delay(1000); // do nothing
}

Other Steps to Reproduce

No response

Provide your Version of the EP32 Arduino Core (or the IDF Version)

2.3.2

I have checked existing issues, discussions and online documentation

  • I confirm I have checked existing issues, discussions and online documentation
@pschatzmann
Copy link
Owner

With my old IOS devices it is working w/o any problems.
I am not buying any recent Hardware from Apple any more, so I can't reproduce this.

@Leo-Ha
Copy link
Author

Leo-Ha commented Apr 7, 2024

Is there a way to add AAC codec support? I think it may be due to ios not liking the SBC codex too much.

With my old IOS devices it is working w/o any problems. I am not buying any recent Hardware from Apple any more, so I can't reproduce this.

@pschatzmann
Copy link
Owner

No, as far as I know, only SBC is supported by Espressif

@Leo-Ha
Copy link
Author

Leo-Ha commented Apr 7, 2024

@pschatzmann
Copy link
Owner

I don't understand, where is the connection with A2DP ?
Here is the link to the relevant API

@Leo-Ha
Copy link
Author

Leo-Ha commented Apr 7, 2024

I don't understand, where is the connection with A2DP ? Here is the link to the relevant API

Oh okay, in your linked document AAC is mentioned in this

uint8_t m24[ESP_A2D_CIE_LEN_M24]
MPEG-2, 4 AAC audio codec capabilities

do you know what this means?
And would you know is the AAC support limited by hardware on the esp32 or just software?

@pschatzmann
Copy link
Owner

But I did not find any method where you could provide this information....

@Leo-Ha
Copy link
Author

Leo-Ha commented Apr 7, 2024

But I did not find any method where you could provide this information....

I dont really understand the documentation or what you mean by that, i will try to search if theres a way to add AAC support but seems im out of luck with this one

@bobisaperson1
Copy link

no updates?

@Leo-Ha
Copy link
Author

Leo-Ha commented Jun 25, 2024

no updates?

I never did manage to get the setup working, i think the esp32's and iphones bt codecs are just incompitable so that it struggles with the audio. Iphone prefers to use AAC codec while esp32 only supports SBC codec.

@bobisaperson1
Copy link

bobisaperson1 commented Jun 25, 2024

I worked out that if the volume on the IPhone is all the way up and ignoring the background noise issue mentioned in a different issue then the sound is as good as you would expect, it’s only when the volume is part way that it sounds horrible, and I testing with a cheap android I have laying around and fount the same thing, is there any way of “blocking” the volume from changing when connected, like how on Mac when connected to a usb DAC the volume bar is greyed out and can’t be changed

@harryberlin
Copy link

do you have an example for "very poor audio quality"?

@bobisaperson1
Copy link

I’ll try recording it and come back here later with a link

@bobisaperson1
Copy link

https://audio.com/bfulham/audio/esp32-test

it came out different to how it sounded straight off the esp32 as the recording has more background noise even at the full volume and didnt distort as much when at part volume compared to earphones off the esp32

@bobisaperson1
Copy link

bobisaperson1 commented Jun 26, 2024

I have ordered an external dac and amp (Adafruit I2S 3W Class D Amplifier Breakout - MAX98357A) to test if this is still an issue without using the internal dac

@pschatzmann
Copy link
Owner

If this is with your internal DAC, my first guess would be a noisy power supply
Did you test with a simple sine test sketch ?

@bobisaperson1
Copy link

I did not but as I have ordered the DAC I am not going to worry about doing further testing, I also noticed an error on the readme for that test sketch and have submitted changes

@Leo-Ha
Copy link
Author

Leo-Ha commented Jun 26, 2024

I have ordered an external dac and amp (Adafruit I2S 3W Class D Amplifier Breakout - MAX98357A) to test if this is still an issue without using the internal dac

I can record the sound later with my 16bit dac i have for the project and connecting that to an aux speaker. I will also try with the esp32S3 instead of the basic, maybe it has some improvments made.

@MarcelNox
Copy link

Out of necessity, I installed Squeezelite on the ESP. I had no problem with the same DAC using an iPhone. Unfortunately, since I do not own an iPhone myself, I couldn't generate logs with A2DP. However, the issue with the DAC can be ruled out.

@mooballs
Copy link

I had some luck with the OP's issue, see here. Might be something else, but it worked for me.

@SaKiEQ
Copy link

SaKiEQ commented Dec 14, 2024

I am having same issue, I have the latest of iOS and macOS devices and some older ones.

Old iOS: 12.5.7
New iOS: 18.2
New MacOS: 15.2

All Apple versions do not work for me giving the same stuttering result.
On the same configuration an Android device place ok.

The audio is super choppy, it seems a buffering / flow rate problem on the a2dp side.
I switched from audio-tools i2s implementation to native ESP32 i2s implementation with no difference.
Changing i2s buffer count and size does not make a difference.

@pschatzmann
Copy link
Owner

pschatzmann commented Dec 14, 2024

Did you try

  • the queued implementation and play around with it's parameters ?
  • change the A2DP_I2S_MAX_WRITE_SIZE ?

@SaKiEQ
Copy link

SaKiEQ commented Dec 14, 2024

let me give that a try.

I was trying changing this values, if event_queue_size gets smaller the sound becomes somewhat better, but even then not usable.
int event_queue_size = 40;
int event_stack_size = 3072;

@pschatzmann
Copy link
Owner

pschatzmann commented Dec 14, 2024

Hmm, that does not make any sense: I would have expected the opposite.
I suggest you send the Output to a MeasuringStream to check the receiving speed.
You can also activate the A2DP log level Debug and compare if IOS is sending more / different messages then Android while transmitting audio

@SaKiEQ
Copy link

SaKiEQ commented Dec 14, 2024

Cool thanks. Right now I need to eat - my nerves are itching and my patience fading :)

I am pretty sure from all the trials and errors so far, it's just a data rate / buffer missmatch.
When I tweak the numbers it gets better until it just stays the same (bad), but it does get better
to a certain level.
The out of the box default values made the sound choppy to the level I feared for my speakers membranes.

Also when I make drastic value changes, the BT connection has problems remaining connected and drops out quit quickly.

Your diagnostic tip will certainly give me more insights !

@SaKiEQ
Copy link

SaKiEQ commented Dec 14, 2024

Hi, I built a MeasuringStream sketch and ran it back to back on iOS and Android.

Here is the measure sketch:

`/**
 * @file streams-a2dp-serial.ino
 * @author Phil Schatzmann & SaKiE
 * @brief a2dp -> is2 MeasuringStream
 * 
 * @author Phil Schatzmann
 * @copyright GPLv3
 * 
 */

#include "AudioTools.h"
#include "AudioTools/AudioLibs/A2DPStream.h"
#include "AudioTools/AudioLibs/AudioBoardStream.h"

#define I2S_BCK   26
#define I2S_WS    25
#define I2S_DAT   22


A2DPStream a2dp_in;
I2SStream i2s_out;

// Measure throughput
MeasuringStream measure(200, &Serial);

StreamCopy copier(measure, a2dp_in); // copy in to MeasuringStream
StreamCopy copier_i2s(i2s_out, a2dp_in); // copy in to out

// Arduino Setup
void setup(void) {
  Serial.begin(115200);
  Serial.flush();
  delay(3000); // wait for serial to become available
  
  AudioToolsLogger.begin(Serial, AudioToolsLogLevel::Warning);

  // start the a2dp bluetooth audio receiver
  Serial.println("starting A2DP...");
  auto cfg = a2dp_in.defaultConfig(RX_MODE);
  cfg.name = "AudioKit";
  a2dp_in.begin(cfg);  

  // setup the i2s
    auto cfgk = i2s_out.defaultConfig(TX_MODE);
                cfgk.copyFrom(a2dp_in.audioInfo());
                cfgk.pin_bck = I2S_BCK;
                cfgk.pin_ws = I2S_WS;
                cfgk.pin_data = I2S_DAT;
    i2s_out.begin(cfgk);

    copier.begin();
    copier_i2s.begin();

    // Serial.println("Starting through-put measurement | Apple iOS 18.2 AppleMusic...");
    Serial.println("Starting through-put measurement | Android 14 AppleMusic...");
}

// Arduino loop  
void loop() {
  copier.copy();
  //copier_i2s.copy();
}`

Apple iOS 18.2 | Source AppleMusic:
21:01:47.648 -> starting A2DP...
21:01:54.509 -> [W] A2DPStream.h : 323 - ==> state: Connecting
21:01:55.101 -> [W] A2DPStream.h : 323 - ==> state: Connected
21:01:55.166 -> Starting through-put measurement | Apple iOS 18.2 AppleMusic...
21:02:01.005 -> ==> Bytes per second: 12000
21:02:03.343 -> ==> Bytes per second: 87000
21:02:05.646 -> ==> Bytes per second: 88000
21:02:07.988 -> ==> Bytes per second: 88000
21:02:10.295 -> ==> Bytes per second: 88000
21:02:12.604 -> ==> Bytes per second: 88000
21:02:14.943 -> ==> Bytes per second: 87000
21:02:17.249 -> ==> Bytes per second: 88000
21:02:19.594 -> ==> Bytes per second: 87000
21:02:21.901 -> ==> Bytes per second: 88000
21:02:24.210 -> ==> Bytes per second: 88000
21:02:26.547 -> ==> Bytes per second: 87000

Android 14 | Source AppleMusic
21:05:19.745 -> starting A2DP...
21:05:27.558 -> [W] A2DPStream.h : 323 - ==> state: Connecting
21:05:27.623 -> [W] A2DPStream.h : 323 - ==> state: Connected
21:05:28.251 -> Starting through-put measurement | Android 14 AppleMusic...
21:05:32.271 -> ==> Bytes per second: 11000
21:05:34.314 -> ==> Bytes per second: 89000
21:05:36.390 -> ==> Bytes per second: 89000
21:05:38.431 -> ==> Bytes per second: 88000
21:05:40.505 -> ==> Bytes per second: 87000
21:05:42.544 -> ==> Bytes per second: 89000
21:05:44.622 -> ==> Bytes per second: 87000
21:05:46.664 -> ==> Bytes per second: 90000
21:05:48.741 -> ==> Bytes per second: 87000

I played the same song from the same platform to be sure. On Android the sound is fine, on iOS choppy.
I get a same sounding result when I leave both copiers running at the same time in the Loop, even on Android. One feeding
the MeasuringStream and one the i2s, I assume that's just a limitation of an ESP32 ?

@pschatzmann
Copy link
Owner

pschatzmann commented Dec 14, 2024

Ah, no just do the following to measure the transmission speed:

#include "AudioTools.h"
#include "BluetoothA2DPSink.h"

MeasuringStream out(10, &Serial);
BluetoothA2DPSink a2dp_sink(out);

void setup() {
    out.setFrameSize(4); // 2 bytes * 2 channels -> output samples/second 
    out.begin();
    a2dp_sink.start("MyMusic");
}

Sending the output to i2s is throtteling it down to the output speed. If you would want to send it to both you would need to use a MultiOutput. With your logic you copy half of the data to I2S and the other half to the MeasuringStream.

@SaKiEQ
Copy link

SaKiEQ commented Dec 14, 2024

hence me sharing the sketch - I thought I missed something... started with your library about 4 days ago...

@SaKiEQ
Copy link

SaKiEQ commented Dec 14, 2024

Off tangent - so StreamCopy empties the buffer after each copy, hence the splitting of the data half here and half there ?
I think that makes perfect sense and explains a lot of issues unrelated to this :).

@pschatzmann
Copy link
Owner

Strange: I would have expected these values to be much bigger.

@Leo-Ha
Copy link
Author

Leo-Ha commented Dec 14, 2024

I am having same issue, I have the latest of iOS and macOS devices and some older ones.

Old iOS: 12.5.7 New iOS: 18.2 New MacOS: 15.2

All Apple versions do not work for me giving the same stuttering result. On the same configuration an Android device place ok.

The audio is super choppy, it seems a buffering / flow rate problem on the a2dp side. I switched from audio-tools i2s implementation to native ESP32 i2s implementation with no difference. Changing i2s buffer count and size does not make a difference.

When i had this issue, i tried all kinds of resolutions but nothing worked. My goal was to convert bluetooth audio to aux for my car and control the playback, so I ended up using a dedicated bluetooth audio conversion device that supports AAC and an ESP32 to control the playback through acting as a ble keyboard and using keyboard playback controls

@Leo-Ha Leo-Ha closed this as completed Dec 14, 2024
@Leo-Ha Leo-Ha reopened this Dec 14, 2024
@SaKiEQ
Copy link

SaKiEQ commented Dec 14, 2024

I am at sea level - hence the air density is highest here - maybe more resistive to push data packages through the air :)
(Sorry just kidding of corse about the air density being a factor).

@pschatzmann
Copy link
Owner

If the issue is really that the receiving speed is not fast enough you could try to play with the i2s sample rate (e.g. setting it to 44050) or by resampling with a small factor e.g. 1.05 to provide more samples.

For this you would need to do the output via the callback, because in the standard functionality, the sample rate is updated from the source.

@SaKiEQ
Copy link

SaKiEQ commented Dec 14, 2024

Ok, yet both the Android and iOS bit rates look about the same, one works fine the other stutters.
My assumption would be that Apple is using smaller data junks in the BLE stream than Android does.

@pschatzmann
Copy link
Owner

I think it is just the other way round. Set the log level to debug: this should show the i2s write size

@SaKiEQ
Copy link

SaKiEQ commented Dec 14, 2024

Here we go,

Apple sends larger junks, most likely overflowing the buffer slightly ?

Apple iOS

22:58:17.868 -> [D] I2SCodecStream.h : 125 - I2SStream::write: 4096
22:58:17.868 -> [D] I2SStream.h : 116 - I2SStream::write: 4096
22:58:17.868 -> [D] I2SESP32V1.h : 106 - size_t audio_tools::I2SDriverESP32V1::writeBytes(const void*, size_t)
22:58:17.899 -> [D] I2SCodecStream.h : 125 - I2SStream::write: 4096
22:58:17.899 -> [D] I2SStream.h : 116 - I2SStream::write: 4096
22:58:17.899 -> [D] I2SESP32V1.h : 106 - size_t audio_tools::I2SDriverESP32V1::writeBytes(const void*, size_t)
22:58:17.899 -> [D] I2SCodecStream.h : 125 - I2SStream::write: 4096
22:58:17.899 -> [D] I2SStream.h : 116 - I2SStream::write: 4096
22:58:17.933 -> [D] I2SESP32V1.h : 106 - size_t audio_tools::I2SDriverESP32V1::writeBytes(const void*, size_t)
22:58:17.933 -> [D] I2SCodecStream.h : 125 - I2SStream::write: 4096

Android

2:57:06.853 -> [D] I2SCodecStream.h : 125 - I2SStream::write: 3584
22:57:06.853 -> [D] I2SStream.h : 116 - I2SStream::write: 3584
22:57:06.853 -> [D] I2SESP32V1.h : 106 - size_t audio_tools::I2SDriverESP32V1::writeBytes(const void*, size_t)
22:57:06.885 -> [D] I2SCodecStream.h : 125 - I2SStream::write: 3584
22:57:06.885 -> [D] I2SStream.h : 116 - I2SStream::write: 3584
22:57:06.885 -> [D] I2SESP32V1.h : 106 - size_t audio_tools::I2SDriverESP32V1::writeBytes(const void*, size_t)
22:57:06.885 -> [D] I2SCodecStream.h : 125 - I2SStream::write: 3584
22:57:06.917 -> [D] I2SStream.h : 116 - I2SStream::write: 3584
22:57:06.917 -> [D] I2SESP32V1.h : 106 - size_t audio_tools::I2SDriverESP32V1::writeBytes(const void*, size_t)
22:57:06.917 -> [D] I2SCodecStream.h : 125 - I2SStream::write: 3584
22:57:06.917 -> [D] I2SStream.h : 116 - I2SStream::write: 3584
22:57:06.917 -> [D] I2SESP32V1.h : 106 - size_t audio_tools::I2SDriverESP32V1::writeBytes(const void*, size_t)
22:57:06.949 -> [D] I2SCodecStream.h : 125 - I2SStream::write: 3072
22:57:06.949 -> [D] I2SStream.h : 116 - I2SStream::write: 3072
22:57:06.949 -> [D] I2SESP32V1.h : 106 - size_t audio_tools::I2SDriverESP32V1::writeBytes(const void*, size_t)
22:57:06.981 -> [D] I2SCodecStream.h : 125 - I2SStream::write: 3584
22:57:06.981 -> [D] I2SStream.h : 116 - I2SStream::write: 3584
22:57:06.981 -> [D] I2SESP32V1.h : 106 - size_t audio_tools::I2SDriverESP32V1::writeBytes(const void*, size_t)
22:57:06.981 -> [D] I2SCodecStream.h : 125 - I2SStream::write: 3584
22:57:06.981 -> [D] I2SStream.h : 116 - I2SStream::write: 3584

@pschatzmann
Copy link
Owner

Did you try to set the A2DP_I2S_MAX_WRITE_SIZE to different values. I added it to split up the write into smaller sizes.
But maybe to change it to 4096, so that it is not splitting it up ?

@SaKiEQ
Copy link

SaKiEQ commented Dec 14, 2024

I am trying those things right now, but you know 2-3min of compile and upload with each change...

I think I2S_BUFFER_SIZE 1024 should be in the same ballpark as A2DP_I2S_MAX_WRITE_SIZE ?

@pschatzmann
Copy link
Owner

No, that should be independent: I think the total i2s buffer size is I2S_BUFFER_SIZE * I2S_BUFFER_COUNT

@SaKiEQ
Copy link

SaKiEQ commented Dec 14, 2024

What if the problem is actually not in the handling of the data, but the BT implementation in Esspressive has a buffer limit ?
I see the data rate always capped at 4096, regardless if I double the A2DP_BUFFER_SIZE, or increase or decrease the A2DP_I2S_MAX_WRITE_SIZE.
What if the data gets truncated before we even get to deal with it ?

@pschatzmann
Copy link
Owner

pschatzmann commented Dec 14, 2024

If that would be the case, there is nothing we could do about this here. But lets double check if adjusting the output sample rate makes a difference. In BluetoothA2DPOutput.cpp goto set_sample_rate() and try something like this

  info.sample_rate = m_sample_rate * 0.98;

And try to play around with this factor.

@pschatzmann
Copy link
Owner

To test your assumption you could write the data to a file

@SaKiEQ
Copy link

SaKiEQ commented Dec 14, 2024

I tried your suggestion of info.sample_rate = m_sample_rate * 0.98;, but it does not make an audible difference - It stills sounds like a bad techno dance party, stuck inside a washing machine...

@pschatzmann
Copy link
Owner

What happens if you decrease this value ?

@SaKiEQ
Copy link

SaKiEQ commented Dec 14, 2024

That is what is happening just right now * 0.5 ;)

@SaKiEQ
Copy link

SaKiEQ commented Dec 14, 2024

It's a bit like somebody tries to make music using morse code... much worse on this low setting.

@SaKiEQ
Copy link

SaKiEQ commented Dec 14, 2024

I even snuck a typo into the code, just to make sure it is the actual file that I am modifying that is being compiled... And the compiler threw an error - hence the chances correct but ineffective.

@pschatzmann
Copy link
Owner

pschatzmann commented Dec 14, 2024

That would speak for your theory.
Hmm, do you happend to have an SD card to store the received data ?

Another thing you could try is to double check if Arduino ESP32 2.0.17 or 3.10 RC3 are having the same issue

@SaKiEQ
Copy link

SaKiEQ commented Dec 14, 2024

IMG_8144 copy
Hahah - are you a bit of mind reader ?

Look what's in the upper left corner ;)

@SaKiEQ
Copy link

SaKiEQ commented Dec 14, 2024

I am downgrading the ESP Libraries... but its slow as snails...

@pschatzmann
Copy link
Owner

Another thought: does it make a difference if you call a2dp_sink.set_avrc_metadata_attribute_mask(0); before begin ?

@SaKiEQ
Copy link

SaKiEQ commented Dec 14, 2024

It's still downloading the Espressif board definitions, I think their server has a. buffer problem too... it's now like 30min downloading. Its now our internet line, I am above 120Mbs download speed...

I will try above when I am ready to compile again.

I still like where you where going with the SD card, writing the data back into an audio file to see if its complete ?

I am also following some breadcrumbs into the A2DP implementation, it seems there is a ring buffer at the core, but I could not figure out the size definitions yet.

@SaKiEQ
Copy link

SaKiEQ commented Dec 14, 2024

the a2dp_sink.set_avrc_metadata_attribute_mask(0); did not change anything. I think you thought if no meta data is requested it would lower the data rate ?

@pschatzmann
Copy link
Owner

Just wanted to make sure that the metadata was not the cause for this issue!

@SaKiEQ
Copy link

SaKiEQ commented Dec 14, 2024

Would not surprise me, if DRM has something do with it. Apple might 'scramble' the data on purpose...

Some Bluetooth stacks enforce the SCMS-T digital rights management (DRM) scheme. In these cases, it is impossible to connect certain A2DP headphones for high quality audio

@SaKiEQ
Copy link

SaKiEQ commented Dec 15, 2024

Here is another insight, this time taken on macOS when connected to the a2dp device.

I assume the ReTX value is re-transmit after failed AK or other Error...

bluetoothd A2DP LinkQualityReport: Handle = 19 ReTx = 70.4% (133 / 189), NAK = 0, NoSync = 133, TxPwr = 11 dBm, RSSI = -48, {50,75,90}th Noise = { -97, -96, -96} for 20 ch, 2EDR pkts = 0, 3EDRTx pkts = 0, HDR4Tx pkts = 0, HDR8Tx pkts = 0, 1SlotTx pkts = 0, 3SlotTx pkts = 0, 5SlotTx pkts = 0, CoexDenial = 0, CoexRequest = 0, 3EDRRx pkts = 0, HDR4Rx pkts = 0, HDR8Rx pkts = 0, 1SlotRx pkts = 0, 3SlotRx pkts = 0, 5SlotRx pkts = 0, TxFlush pkts = 0, Tx2Flush pkts = 0, rate = 327 kbps Flow off = 0 devicename =

@SaKiEQ
Copy link

SaKiEQ commented Dec 15, 2024

And I can confirm the iOS/macOS is selecting the 'correct' codec - not defaulting to AAC as somewhere suggested.

14:22:16.572299+0300 bluetoothd A2DP configured at 44.1 KHz. Codec: SBC, Bitpool: 53 (327 kbps). 8 frames * 119 bytes = 952 per RTP (max=988) every 23.22 ms

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants