Skocz do zawartości

I2S - Szum po softowej zmianie głośności


Pomocna odpowiedź

Napisano

Dzień dobry,

Bawię się ESP32 i piszę swoją bibliotekę audio wykorzystując do tego interfejs I2S, wzmacniacz mono MAX98357A i głośniczek 4 Ohmowy. Do tego mam dołączony kondensator 100nF (Vcc-GND) i 220uF (Vcc-GND). 

Dźwięki, które odtwarzam to pliki w formacie .wav. Generalnie wszystko ładnie działa na oryginalne plików - nie ma żadnych szumów podczas odtwarzania. Chciałbym móc regulować w sposób softowy głośność granych dźwięków bez angażowania do tego hardware'u (mogę przez PWN sterować wejściem SD, ale chcąc oszczędzić port chciałbym zrobić to jednak w sposób softowy). Obecnie robię to używając równania: wartość_aktualnej_próbki = wartość_aktualnej_próbki * (poziom_głośności / 100), gdzie poziom_głośności ustawiany jest w zakresie 0-100 i to powinno dawać głośność z zakresu 0-100%. 

Po testach, poziom 100% działa idealnie, a później im niżej tym większe pojawiają się szumy podczas odtwarzania. Przy poziomach poniżej 50% następuje jakieś dziwne wzmocnienie sygnału i mega zaszumienie (gra głośniej niż przy poziomie 100%). Sprawdziłem wartości jakie zapisywane są po dostosowywaniu głośności do odpowiedniego poziomu i sam kod generowany jest prawidłowo. Wydaje mi się, że problem jest bardziej sprzętowy niż programowy stąd pytanie do bardziej doświadczonych w audio, co może być problemem narastających szumów z obniżaniem głośności?

Dodam, że wszystko jest spięte na płytce stykowej i domyślam się, że przy niskich poziomach napięcia stosunek S/N jest na korzyść szumów i pewnie polutowanie wszystkiego na PCB sporo by pomogło, ale chciałbym się upewnić albo ulepszyć swój obwód przed polutowaniem wszystkiego.

 

(edytowany)
10 minut temu, Elvis napisał:

A jakiego typu są te dane, tzn. jaki jest typ zmiennej "wartość_aktualnej_próbki"?

To są zmienne typu double, a później po obliczeniu rzutuje sobie to na uint8_t. Pomyłka jest maksymalnie o 1 bit przy takiej operacji, bo po rzutowaniu liczba jest zaokrąglona o 1 do góry lub w dół. Może to jest problem?

Tutaj daję kod jak to wygląda:

   // buffer_size to długość bufora do którego ładowane są dane do wysłania przez I2S, standardowo 1024 
	
	for(int buffer_position = 0; buffer_position < buffer_size; buffer_position++)
        {
        	double tmp_calc = 0;
        	double CURRENT_VOLUME_RATE = 1; // tutaj równie dobrze może być (wartość_glośności / 100)
        	if(buffer[buffer_position] != 0)
        	{
        		tmp_calc = buffer[buffer_position] * CURRENT_VOLUME_RATE;
        		buffer[buffer_position] = static_cast <uint8_t> (tmp_calc);
        	}

 

Edytowano przez Chev

Problemem raczej nie jest utrata jednego, najmniej znaczącego bitu.

Ale może warto zastanowić się jakie dane odbiera MAX98357A, czy to na pewno są bajty (uint8_t)? Z dokumentacji na szybko:

Cytat

The ICs operate using 16/24/32-bit data for I2S and left-justified modes as well as 16-bit or 32-bit data using TDM mode

O trybie 8-bitowym niewiele można wyczytać. A jeśli masz dane np. 16-bitowe, ale sobie każdy składowy bajt przemnożysz przez dany współczynnik, to niekoniecznie uzyskasz to czego oczekujesz. 

Powstaje też pytanie o znak danych, czy to na pewno uint8_t, albo prędzej uint16_t, czy raczej int16_t?

  • Lubię! 1

Na pewno bez znaku, bo tak mam eksportowany plik (unsigned int 8 bit). Co prawda wysyłam buffor (uint8_t) z 1024 próbkami przez I2S i dla głośności 100% jest wszystko okej, ale może faktycznie sam wzmacniacz oczekuje danych 16 bitowych, a to że obecnie działa jest przypadkiem i błędem polegającym na zasugerowaniu się typem samego pliku audio.

 

Przepiszę trochę kod i dam znać, bo chyba faktycznie powinienem ładować przez I2S dane typu uint16_t, a nie uint8_t.

Skoro przy "pełnej głośności" dźwięk jest odtwarzany poprawnie, to pewnie I2S działa zgodnie z oczekiwaniami.

Problemem mogą być natomiast obliczenia. Załóżmy, że mamy dwie kolejne, 16-bitowe próbki o wartościach odpowiednio 255 i 256. Różnica między nimi jest niewielka, więc jeśli odtwarzamy je z 50% głośności oczekujemy też niewielkiej różnicy. Ale policzmy:

Dla 255 mamy szesnastkowo 0x00ff (starszy bajt 0, młodszy 0xff). Jak podzielimy 0x00 przez 2 (co daje 50%), mamy 0x00. Dzielenie 0xff przez 2 to jakieś 0x7f. Czyli wynik 0x007f, albo 127 dziesiętnie - wszystko wygląda ok.

Teraz liczymy dla 256. Wartość szesnastkowo 0x0100 (starszy bajt 0x01, młodszy 0x00). Dzielenie 0x01 przez 2 daje 0x00, czyli wynik to 0x0000... 

Program z wartości 255, 256 tworzy 127, 0 - czyli gwałtowną zmianę sygnału, a to już może być słyszalne.

Jeszcze gorzej może być w przypadku liczb ze znakiem - a warto upewnić się, czy aby nie są to dane int16_t. Przykładowo wikipedia coś o tym wspomina: https://en.wikipedia.org/wiki/I²S

  • Lubię! 1
(edytowany)

Jakby co, taki standarowy WAVE, jakość CD 44100 Hz to 16 bitów ze znakiem. Dwa bajty lewy, dwa bajty prawy i tak do końca. Poza nagłówkiem i meta danymi oczywiście. Little endian (PS. na Wiki piszą, że I2S MSB first). Może wysyła też, jak mówisz 1024 próbki (to ile bajtów wysyłasz?) i przypadkiem dzieli na pół jedną próbkę. W sumie po co double? Ciekawe rzeczy mogą się dziać. Np, usuwając jeden bajt z utworu, czy coś w tym stylu, wyszła prawie cała piosenka szum, widać po wykresie w Audacity, a końcówka piosenki wyłaniała się przez kilka sekund do postaci praktycznie bez szumu.

Edytowano przez matsobdev

Bądź aktywny - zaloguj się lub utwórz konto!

Tylko zarejestrowani użytkownicy mogą komentować zawartość tej strony

Utwórz konto w ~20 sekund!

Zarejestruj nowe konto, to proste!

Zarejestruj się »

Zaloguj się

Posiadasz własne konto? Użyj go!

Zaloguj się »
×
×
  • Utwórz nowe...