Elektroda.pl
Elektroda.pl
X
Elektroda.pl
PCBway
Proszę, dodaj wyjątek dla www.elektroda.pl do Adblock.
Dzięki temu, że oglądasz reklamy, wspierasz portal i użytkowników.

[AT91SAM7S256][C/Rowley Crossworks] Trzeszczący dźwięk z MP3

26 Sie 2009 16:05 14016 103
  • Poziom 14  
    Witam wszystkich

    Zrobiłem sobie odtwarzacz MP3 na mikrokontrolerze ARM AT91SAM7S256.Jako przetwornik D/A zastosowałem UDA1330ATS (poprzednio był TDA1545). D/A współpracuje z ARM-em poprzez interfejs I2S zrobiony na SSC według not aplikacyjnych Atmela (AT91SAM Internet Radio oraz Connecting an I2S Audio DAC to the AT91x40 Series). Korzystałem z bibliotek dekodujących MP3 z Helix Community. I wszystko działa, tyle, że w czasie odtwarzania muzy słychać jeszcze trzeszczenie. Słuchawki od walkmana podłączyłem bezpośrednio do wyjść UDA1330, oczywiście poprzez układ RC (zaczerpnięty z pdf-a od UDA1330). Dane do przetwornika D/A transferowane są z wykorzystaniem kanału DMA. Pokusiłem się o nagranie sinusoidy 1kHz w formacie MP3, dołączam oscylogram tej sinusoidy na wyjściach UDA1330. Te "duchy" widoczne wokół właściwego wykresu sinusa są właśnie słyszalne jako to trzeszczenie. Ktoś wie, jak poprawić jakość dźwięku? Zetknął się ktoś już z tym problemem?


    [AT91SAM7S256][C/Rowley Crossworks] Trzeszczący dźwięk z MP3

    Dla zainteresowanych
    Odtwarzacz jest na razie na płycie ewaluacyjnej z Propox-u (dołączam zdjęcie).

    [AT91SAM7S256][C/Rowley Crossworks] Trzeszczący dźwięk z MP3

    Na razie odtwarza pliki z bitrate'm 128...320kbit/s odczytywane z karty SDHC 8GB. Zaimplementowałem w programie playerka obsługę FAT32 na karcie SD, długich nazw plików i podkatalogów oraz Mass Storage Device na USB do przerzucania plików między kartą SD i kompem. Mikrokontroler chodzi mi na 67,7376MHz, gdy odtwarza pliki MP3 z fs=44,1kHz, 48MHz, gdy podłączone USB. Wyświetlacz LCD - tekstowy, 2x16 znaków. Podczas odtwarzania przewija się na nim nazwa odtwarzanego pliku oraz całkowity czas trwania utworu, bieżący czas odtwarzania i bitrate. Gdy nie ma odtwarzania, to na LCD jest przewijana przyciskami lista plików i podkatalogów na karcie SD. Mogę wrzucić kod całego dotychczasowego programu, ale na razie utknąłem na jakości dźwięku.

    Pozostało mi jeszcze do zrobienia:
    obsługa podkatalogów (wejście do nich, wylistowanie ich zawartości oraz wyjście z nich do katalogu nadrzędnego); przycisk STOP zatrzymujący odtwarzanie muzy;
    regulacja głośności przyciskami przewijania w górę i w dół listy plików i podkatalogów (UDA1330 ma możliwość regulacji głośności); odtwarzanie plików z innym fs.
    Na koniec zaprojektowanie PCB i polutowanie całości. Docelowo ma być to mały playerek na słuchawki. Przewiduję użycie scalaków: AT91SAM7S256, UDA1330 oraz może jakaś scalona przetwornica DC/DC (playerek zasilany z jednego lub dwóch paluszków R3 lub takich akumulatorków) oraz mały LCD 2x16 (znalazłem w TME)

    Pozdrawiam:)
    KT_priv
  • PCBway
  • Poziom 14  
    THX za odpowiedź DJ.TrOX:) Spróbuję wieczorem wlutować te rezystory - zobaczę, co z tego wyjdzie.
    Pozdrawiam, KT_priv

    Dodano po 4 [godziny] 13 [minuty]:

    Witam ponownie
    Spróbowałem z rezystorami 27ohm wstawionymi w linie BCK, WS, DATAI przetwornika UDA1330. Niestety, żadnej poprawy. Popróbuję jeszcze może z innymi wartościami rezystorów, ale to dopiero jutro. W każdym razie dzięki za podpowiedź:) Może ma ktoś jeszcze jakiś inny pomysł ?:) Pozdrawiam, KT_priv
  • Poziom 15  
    Spróbuj wprowadzić takie ustawienia interfejsu SSC:

    Code:

    #define SSC AT91C_BASE_SSC

    SSC->SSC_TCMR = AT91C_SSC_TCMR_CKS_TK | //Select Clock Source TK Pin
                    AT91C_SSC_START_EDGE_RF  | //Start on any edge TF       
                    (0<<16);            // STTDLY = 0!
                   
       
    SSC->SSC_TFMR = (16-1) |  //16 bit word lenght
                   ((1-1)<<8) |      // DATNB = 0 => 1 words per frame
                   AT91C_SSC_MSBF;      //Most Significant Bit First


    Gdy ja próbowałem skonfigurować SSC jako I2S wg wspomnianej noty Atmela nt tej konfiguracji, zupełnie to nie działało. W końcu podglądnąłem jak to robią inni i okazało sie np, że pole STTDLY, które w teorii powinno być ustawione na 1, w praktyce powinno być równe 0.
  • Poziom 14  
    Witam ponownie
    Najnowszy meldunek:
    Podstawienie na żywca do rejestrów TCMR i TFMR danych zaproponowanych przez radiodę - kompletna cisza w słuchawkach.

    Poniżej mój kod konfigurujący rejestry SSC:
    Code:
    //AT91F_SSC_Configure( AT91C_BASE_SSC, 67737600, (2*16*sampling_freq), 0, 0, AT91C_I2S_ASY_MASTER_TX_SETTING(16, 2), AT91C_I2S_ASY_TX_FRAME_SETTING(16, 2)); //pierwsza wersja konfigu SSC z wykorzystaniem danych z pliku lib_AT91SAM7S256.h
    
                                    AT91F_SSC_Configure( AT91C_BASE_SSC, 67737600, (2*16*sampling_freq), 0, 0, 0,0); //druga wersja konfigu SSC
                             
                              *AT91C_SSC_TCMR = AT91C_SSC_CKS_TK | //Select Clock Source TK Pin
                                                AT91C_SSC_CKO_CONTINOUS |
                                                AT91C_SSC_START_EDGE_RF  | //Start on any edge TF       
                                                (1<<16)  |            // STTDLY = 0! ;
                                                ((((16 * 2)/2) - 1) <<24);
                              *AT91C_SSC_TFMR = (16-1) |  //16 bit word lenght
                                                AT91C_SSC_MSBF |
                                                ((1-1)<<8) |      // DATNB = 0 => 1 words per frame
                                                (((16-1)<<16) & AT91C_SSC_FSLEN) |
                                                AT91C_SSC_FSOS_NEGATIVE;

    Wprowadziłem modyfikacje w danych wpisywanych do TCMR i TFMR (uwzględniłem dane radiody), żeby był dźwięk w słuchawkach - trzeszczenie jest nadal. Bez tego trzeszczenia dźwięk byłby OK. STTDLY musi być 1 u mnie, jeśli UDA1330 ma interfejs I2S, STTDLY = 0 dla interfejsu LSB justified format,16bit w UDA1330. Gdy wstawiałem inne STTDLY, to nie dało się tego słuchać.
    Do DJ.TRoX - rezystory nie pomogły. Wstawiłem PR-ki 1,5k i regulacja nimi od 0 do max nie dawała efektu.
    Ciekaw jestem, jak to autor zrobił w odtwarzaczu MP3 (przetwornik UDA1330, ARM9) opisanym w EP7/2009, że uzyskał dobrą jakość dźwięku. Była tam pierwsza część, niestety drugiej części artykułu jeszcze nie mam.
    Pozdrawiam, KT_priv
    PS: będę dopiero wieczorem
  • Specjalista - Mikrokontrolery
    Skąd masz 100% pewności, że dane które przepychasz przez magistralę są dobre? "Duchami", jak to nazwałeś, bym sie nie przejmował - zapewne źle ustawione wyzwalanie oscyloskopu. Za to na tych "duchach" jest słabo widoczne zakłócenie - dwukrotnie, na zboczu opadającym pojawia się zafalowanie. To jest twój prawdziwy problem.

    Ciężko będzie dojść do tego z czego wynika ten problem - może dane są błedne, może zakłócone, może transmisja się przytyka i występują chwile ciszy / stałego sygnału.

    Robiłem komunikację procesora z przetwornikami na I2S i nie miałem z tym żadnych problemów - linie interfejsu były dosyć długie (conajmniej 5 cm) i w międzyczasie było połączenie między płytkami (procek na dolnej, przetworniki na dolnej) - żadne problemy się nie pojawiały. fs = 48kHz.

    4\/3!!
  • PCBway
  • Poziom 15  
    Rzecz w tym że ja też używam I2S tyle że z TLV320AIC23B, który pracuje w trybie master czyli sam generuje sygnały TK i TF, więc wklejenie moich ustawień żywcem do twojego projektu zaowocowało ciszą.
    W drugiej wersji ustawień musisz zmienić źródło sygnału TK z pinu na wewnętrzny dzielnik. Poza tym start transmisji na zbocze opadające na TF. Do tego wyrzucił bym całkiem synchronizację.

    Spróbuj najpierw tak:

    Code:
    AT91F_SSC_Configure( AT91C_BASE_SSC, 67737600, (2*16*sampling_freq), 0, 0, 0,0);
    

    *AT91C_SSC_TCMR = AT91C_SSC_CKS_DIV   |
                      AT91C_SSC_CKO_CONTINOUS      |
                      AT91C_SSC_START_FALL_RF |
                      ((0<<16) & AT91C_SSC_STTDLY) |     //STTDLY = 0
                      ((((16*2)/2)-1) <<24);

    *AT91C_SSC_TFMR = (16-1)  |
                      AT91C_SSC_MSBF   |
                     (((2-1)<<8) & AT91C_SSC_DATNB)  |
                     (((16-1)<<16) & AT91C_SSC_FSLEN) |   // impuls synchronizacji
                     AT91C_SSC_FSOS_NEGATIVE;         // impuls synchronizacji


    a jak nie zadziała to pokombinuj z tym polem STTDLY i wycięciem tych dwóch linijek które oznaczyłem jako impuls synchronizacji.

    Być może takie kombinacje z ustawieniami nie mają sensu, ale jak wspomniałem ustawienia proponowane przez atmela u mnie nie chciały działać.

    Możliwe też że trzeszczenie pochodzi z innego źródła jak zauważył kolega wyżej. Jeśli używasz trybu DMA dla SSC sprawdź czy dobrze definiujesz długość bufora PDC. Wartość wpisywana do TCR to ilość słów (nie zawsze bajtów), czyli w Twoim przypadku mp3FrameInfo.outputSamps. Ja np początkowo mnożyłem tą wartość przez dwa z racji że próbka ma 16bit (2Bajty), czego efektem były właśnie trzaski gdyż SSC wysyłał oprócz zdekodowanych danych drugie tyle śmieci.

    Polecam odtworzyć jakiegoś WAVa, by sprawdzić czy problem leży po stronie wysyłania danych do DACa czy może gdzie indziej. Najlepiej zdekompresuj sobie kawałek MP3 zapisując dane na karcie SD jako jakiś plik, a następnie odtwórz te dane proto z karty wysyłając po kolei do DACa. Jeśli odtworzy sie płynnie i bez trzasków to znaczy że z SSC wszystko OK.
  • Poziom 14  
    Witam ponownie
    Chyba coś drgnęło :) Rzeczywiście, być może jest to jednak problem z transferem danych między procem i D/A. Znalazłem być może jedną z przyczyn, mianowicie tablica outBuf z próbkami PCM ma elementy typu short, a wskaźnik do tablicy z danymi ,które chcę przesłać w trybie DMA wywołując funkcję AT91F_SSC_SendFrame( AT91C_BASE_SSC, ...); był typu char. Na tym kompilator mi wywalał ostrzeżenie. Poprawiłem we wszystkich wywoływanych przez tą funkcję dalszych funkcjach typ tego wskaźnika na short i chyba to trzeszczenie ma mniejsze natężenie (na słuch), ale nie pozbyłem się go do końca :( Kompilacja w każdym razie przechodzi bez błędów i ostrzeżeń. Przepraszam wszystkich znających język C, których uraziłem takim sposobem poradzenia sobie z problemem. Jestem jeszcze początkującym w programowaniu w tym języku i takim, jako naturalnym, wydawał mi się sposób rozwiązania tego problemu. Jutro spróbuję powalczyć dalej...
    Dziękuję wszystkim na nakierowanie mnie, być może, na właściwy trop:)
    Spokojnej nocy, pozdrawiam:) KT_priv
    PS: jutro będę dostępny po 22 - mam drugą zmianę w robocie. Wcześniej mnie też nie będzie w domu.

    Dodano po 3 [minuty]:

    Acha, byłbym zapomniał - najlepiej jednak mi gra na Atmel'owych ustawieniach rejestrów TCMR i TFMR (przynajmniej na razie). Pozdr, KT_priv
  • Poziom 15  
    To teraz sprawdź jeszcze czy podajesz dobry rozmiar bufora do AT91F_SSC_SendFrame (możesz nawet na sztywno wpisywać coraz krótsze wartości i słuchać czy trzeszczenie zanika), a później czy dekoder Ci sie "wyrabia" czasowo, czyli czy dekodowanie plus odczyt danych z karty dla dekodera trwa krócej niż odtwarzanie jednej ramki. Najlepiej jednak zrobić tak jak proponowałem wcześniej, czyli odtworzyć nieskompresowane dane PCM z karty. Wtedy ARM na pewno wyrobi sie czasowo więc to podejrzenie odpadnie i będzie można szukać dalej.
  • Poziom 14  
    Wczoraj tak próbowałem. Rozmiar bufora podawałem out_Samples = mp3FrameInfo.outputSamps +-2 i najlepiej było dla wartości mp3FrameInfo.outputSamps (najsłabsze trzeszczenie). Z WAV-em spróbuję, ale muszę napisać dodatkowy kod odtwarzający WAV-y z karty (na razie mam tylko MP3). Robiłem trochę inaczej - gdy playerek odtwarzał muzę,to próbki PCM wysyłałem serialem do kompa z programem terminala, gdzie ten odebrane dane wrzucał do pliku. Z tego pliku zrobiłem plik WAV i gdy go odtworzyłem, to muza ładnie grała, czysto, bez trzasków. Stąd wnioskuję, że outBuf jest wypełniany właściwymi wartościami. Poniżej kod, który wysyła mi dane do D/A
    Code:
    while (! (AT91C_BASE_SSC->SSC_SR & AT91C_SSC_ENDTX));   //odczekanie w pętli, aż wszystkie dane z portu SSC pójdą do przetwornika D/A
    
    AT91F_SSC_SendFrame( AT91C_BASE_SSC, outBuf, out_Samples, 0, 0); //wysłanie do portu SSC poprzez kanał DMA nowych danych dla przetwornika D/A

    Korzystam tylko z jednego bufora - może tu jest problem?
    W każdym razie, gdy wywaliłem powyższą pętlę oczekującą, to muza była zdecydowanie za szybko odtwarzana i chyba też były trzaski (ale już w tej chwili nie pamiętam - muszę sprawdzić). Stąd wnioskuję, że ARM wyrabia się z obliczeniami, skoro musi poczekać w pętli na opróżnienie bufora PDC z poprzednich danych - może się mylę...
    Pozdrawiam, KT_priv
  • Poziom 15  
    Gdy używasz jednego bufora DMA, nie masz podwójnego buforowania, a wtedy nie ma niestety płynności przesyłania danych do DACa. Na początku musisz załadować oba bufory nawet tymi samymi danymi dla uproszczenia, a dalej już w pętli gdy ENDTX=1 podawać wskaźnik i rozmiar nowego bufora, ale wpisując do TNPR (Transmit Next Pointer Register) i TNCR (Transmit Next Counter Register). Np w ten sposób:

    Inicjalizacja buforów PDC:
    Code:

    AT91F_SSC_SendFrame( AT91C_BASE_SSC, outBuf, out_Samples, outBuf, out_Samples);


    Pętla odtwarzania:
    Code:

    while (! (AT91C_BASE_SSC->SSC_SR & AT91C_SSC_ENDTX));   //odczekanie w pętli, aż wszystkie dane z portu SSC pójdą do przetwornika D/A
    AT91F_SSC_SendFrame( AT91C_BASE_SSC, 0, 0, outBuf, out_Samples); //wysłanie do portu SSC poprzez kanał DMA nowych danych dla przetwornika D/A


    Gdy dane z pierwszego bufora sie wyczerpią, moduł PDC przepisze TNPR->TPR i TNCR->TCR, a ty dostaniesz informacje (ENDTX=1), że możesz znów podać informacje o następnym buforze wypełniając pola TNPR i TNCR. W tym przypadku ciągłość jest zachowana o ile zdążysz wpisać dane o nowym buforze zanim wyczerpie się pierwszy.
  • Poziom 14  
    Witam
    Dzięki za kolejną poradę, spróbowałem zastosować Twój kod, niestety bez zmian... Dołączam plik WAV z nagraniem, jak mi playerek odtwarza sinusoidę. Podczas odtwarzania sinusa zmierzyłem też czas oczekiwania w pętli na ENDTX = 1 (wynosi ok.10ms) i czas pobierania nowych danych i ich dekodowania (wynosi ok.15ms). Pomiar wykonałem w taki sposób, że w pętli oczekiwania na ENDTX=1 wystawiałem niski poziom na pin, a po wyjściu z tej pętli wystawiałem na ten pin wysoki poziom i oscylem zmierzyłem czasy trwania niskiego i wysokiego poziomu. Zastosowałem optymalizację kodu wynikowego (optimize for size), bez tej optymalizacji rzeczywiście ARM nie "wyrabiał się" z dostarczaniem na czas nowych danych do D/A. W efekcie odtwarzanie było takie "przerywane".
    Poniżej daję też cały kod pętli dekodującej, może przy całościowym spojrzeniu ktoś zauważy błąd/ błędy, którego/ których ja nie widzę. Sobotę sobie odpuszczam, spróbuję w niedzielę podejść znów do tematu.
    Miłego weekendu, pozdrawiam, KT_priv

    Code:
    do //w tej pętli dekodowanie ramek MP3; wykonuje się tak długo, aż err = -1 (koniec pliku)
    
            {
              offset = MP3FindSyncWord(readPtr, 4096, Frame_header); //poszukiwanie nagłówka ramki MP3 w buforze_we wskazywanym wskaźnikiem readPtr
                //w zmiennej Frame_header jest wzorzec poszukiwanego nagłówka; wyliczany po udanym zdekodowaniu pierwszej ramki MP3 (wtedy err = 0))
              if (offset < 0) { //nie znaleziono nagłówka ramki MP3 w całym 4kB buforze wejściowym wskazywanym wskaźnikiem readPtr
                                start_address = next_cluster(numer_wpisu_poczatku_listy, rodzaj_karty);//obliczenie adresu pierwszego sektora kolejnego klastra pliku
                                if (start_address == 0x0FFFFFFF)
                                {
                                  outOfData = 1;  //gdy start_address == 0x0FFFFFFF (już nie ma więcej klastrów przydzielonych dla wybranego pliku)
                                  break;           //to zakończenie pętli do...while dekodującej ramki MP3
                                }
                                else
                                {  //w przeciwnym razie start_address zawiera adres pierwszego sektora należącego do kolejnego klastra zajętego przez plik i wtedy:
                                  indeks = 0;
                                  for (licznik = 0; licznik < 8; licznik++)
                                  {  //w tej pętli odczytanie 8 sektorów należących do kolejnego klastra wybranego pliku z karty SD i wypełnienie nimi bufora wejściowego bufor_we
                                      indeks = fill_buffer(start_address, licznik, indeks, rodzaj_karty);
                                  }
                                  readPtr = bufor_we;  //teraz wskaźnik readPtr wskazuje pierwszy element tablicy bufor_we z nowymi danymi
                                  Frame_header = 0x00080000;
                                }
                              }
                        else  {
                                readPtr += offset;   
                                Bytes_before = bytesLeft;
                                err = MP3Decode(hMP3Decoder, (err == 0 ? &readPtr : &readPtr1), &bytesLeft, outBuf, 0);  //dekodowanie jednej ramki strumienia MP3 z tablicy bufor_we
                                if (err)
                                { /* error occurred */
                                  //obsługa błędnie zdekodowanej bieżącej ramki MP3 - sprowadza się ona do pominięcia błędnej ramki i dekodowania następnej
                                }
                                else
                                { /* no error */
                                  MP3GetLastFrameInfo(hMP3Decoder, &mp3FrameInfo); //uzyskanie informacji o zdekodowanej ramce MP3
                                  Bitrate_old = mp3FrameInfo.bitrate; //w Bitrate_old jest wartość bitrate zdekodowanej już ramki MP3
                                  if (nFrames == 0) // gdy była to dopiero pierwsza zdekodowana ramka, to najpierw przed wysłaniem danych do portu SSC
                                  {
                                    sampling_freq = mp3FrameInfo.samprate;
                                    AT91F_PMC_EnablePeriphClock( AT91C_BASE_PMC, 1 << AT91C_ID_SSC ) ;  //enable the clock of the SSC
                                    AT91F_SSC_Configure( AT91C_BASE_SSC, 67737600, (2*16*sampling_freq), 0, 0, AT91C_I2S_ASY_MASTER_TX_SETTING(16, 2), AT91C_I2S_ASY_TX_FRAME_SETTING(16, 2));
                                /*    AT91F_SSC_Configure( AT91C_BASE_SSC, 67737600, (2*16*sampling_freq), 0, 0, 0,0);  //inna propozycja nastaw rejestrów TCMR i TFMR
                             
                              *AT91C_SSC_TCMR = AT91C_SSC_CKS_DIV | //Select Clock Source TK Pin
                                                AT91C_SSC_CKO_CONTINOUS |
                                                AT91C_SSC_START_EDGE_RF  | //Start on any edge TF       
                                                (0<<16)  |            // STTDLY = 0! ;
                                                ((((16 * 2)/2) - 1) <<24);
                              *AT91C_SSC_TFMR = (16-1) |  //16 bit word lenght
                                                AT91C_SSC_MSBF |
                                                ((1-1)<<8) |      // DATNB = 0 => 1 words per frame
                                                (((16-1)<<16) & AT91C_SSC_FSLEN) |
                                                AT91C_SSC_FSOS_NEGATIVE; */
                                                 
                                    AT91F_SSC_EnableTx( AT91C_BASE_SSC);  //Enable the TX transmitter
                                    out_Samples = mp3FrameInfo.outputSamps;
                                    AT91F_SSC_SendFrame( AT91C_BASE_SSC, outBuf, out_Samples, outBuf, out_Samples); //wysłanie do portu SSC poprzez kanał DMA danych dla przetwornika D/A i wypełnienie drugiego bufora DMA
                                  }
                                  while (! (AT91C_BASE_SSC->SSC_SR & AT91C_SSC_ENDTX))  AT91F_PIO_ClearOutput( AT91C_BASE_PIOA, AT91C_PIO_PA27);//LED świeci, gdy ENDTX=0  //odczekanie w pętli, aż wszystkie dane z portu SSC pójdą do przetwornika D/A
                                  AT91F_PIO_SetOutput( AT91C_BASE_PIOA, AT91C_PIO_PA27);//LED nie świeci, gdy ENDTX=1
                                  AT91F_SSC_SendFrame( AT91C_BASE_SSC, 0, 0, outBuf, out_Samples); //wysłanie do portu SSC poprzez kanał DMA danych dla przetwornika D/A
                                  Difference = readPtr - bufor_we; //obliczenie różnicy pomiędzy adresem ostatniego przetwarzanego bajtu i początkowym adresem bufora wejściowego
                                  if (! (MP3GetNextFrameInfo(hMP3Decoder, &mp3FrameInfo, readPtr))) //gdy pobranie info o następnej ramce MP3 odbędzie się bez błędu (wtedy następna ramka mieści się w całości w buforze wejściowym)
                                       Bitrate_new = mp3FrameInfo.bitrate; //to w Bitrate_new bedzie wartość bitrate następnej ramki MP3
                                  else Bitrate_new = Bitrate_old;  //w przeciwnym razie w Bitrate_new będzie wartość Bitrate_old
                                  if (Bitrate_old == Bitrate_new)  //gdy jest to prawda (plik Constant BitRate (CBR))
                                  {
                                    Frame_length = Bytes_before - bytesLeft; //długość w bajtach jednej ramki strumienia MP3
                                    if (offset == 0) Frame_header = (*(readPtr - Frame_length) << 24) | (*(readPtr - Frame_length + 1) << 16) |
                                                                    (*(readPtr - Frame_length + 2) << 8) | *(readPtr - Frame_length + 3 ); //złożenie 4-bajtowego nagłówka każdej ramki MP3 z elementów bufora wejściowego wskazywanych poprzez różnice (readPtr - Frame_length + x)
                                  }
                                  else  //w przeciwnym razie jest to plik Variable BitRate (VBR)) i wtedy:
                                  {                                                 
                                    Frame_length = ((mp3FrameInfo.outputSamps / (mp3FrameInfo.nChans * 8)) * mp3FrameInfo.bitrate) / mp3FrameInfo.samprate; //obliczenie długości następnej ramki MP3
                                    Frame_header = (*readPtr << 24) | (*(readPtr + 1) << 16) | (*(readPtr + 2) << 8) | *(readPtr + 3);  //i złożenie nagłówka tej ramki z elementów bufora wejściowego wskazywanych wskaźnikiem readPtr + x
                                  } //wtedy uzyskuję poprawne odtwarzanie plików CBR i VBR
                                  if ((Difference > 2047) & (nBlocks_processed % 2 == 0)) //gdy Difference > 2047 (wtedy zaczął się odczyt z górnej połowy bufora wejściowego)
                                    {  // i gdy nBlock_processed jest jeszcze liczbą parzystą (wtedy jeszcze dolna połowa bufora wejściowego zawiera stare dane,już przetworzone)
                                      nBlocks_processed++;  //to poniżej wypełnienie dolnej połowy bufora wejściowego nowymi danymi z wybranego pliku MP3
                                      if (licznik < opis_partycji.sector_per_cluster[numer_partycji]) //spr, czy licznik odczytanych sektorów < liczby sektorów przypadających na jeden klaster pliku
                                      {   //gdy to sprawdzenie jest prawdą:
                                         indeks = 0;
                                         i = 0;  //licznik poniższej pętli
                                         do
                                         {  //to w tej pętli wypełnienie dolnej połowy bufora wejściowego
                                           indeks = fill_buffer(start_address, licznik, indeks, rodzaj_karty);
                                           licznik++; //zwiększenie licznika odczytanych sektorów należących do klastra pliku
                                           i++;
                                         }     
                                         while (i < 4); //dolna połowa bufora wejściowego mieści 4 sektory, ta pętla wykona się 4 razy
                                      }
                                      else //w przeciwnym razie licznik odczytanych sektorów = liczbie sektorów przypadających na jeden klaster pliku
                                      {    //(czyli zakończyło się odczytywanie bieżącego klastra pliku i trzeba odczytać kolejny klaster zajęty przez odtwarzany plik)
                                         licznik = 0; //wyzerowanie licznika sektorów, bo będzie odczytywany później pierwszy sektor z kolejnego klastra zajętego przez plik
                                         start_address = next_cluster(numer_wpisu_poczatku_listy, rodzaj_karty);//obliczenie adresu pierwszego sektora kolejnego klastra pliku
                                         if (start_address == 0x0FFFFFFF)
                                         {
                                           outOfData = 1;  //gdy start_address == 0x0FFFFFFF (już nie ma więcej klastrów przydzielonych dla wybranego pliku)
                                           break;           //to zakończenie pętli do...while dekodującej ramki MP3
                                         }
                                         else 
                                         {  //w przeciwnym razie start_address zawiera adres pierwszego sektora należącego do kolejnego klastra zajętego przez plik i wtedy:
                                           indeks = 0;
                                           do
                                           { //w tej pętli wypełnienie dolnej połowy bufora wejściowego nowymi danymi z kolejnego klastra należącego do pliku
                                             indeks = fill_buffer(start_address, licznik, indeks, rodzaj_karty);
                                             licznik++; //zwiększenie licznika odczytanych sektorów należących do klastra pliku
                                           }
                                           while (licznik < 4); //dolna połowa bufora wejściowego mieści 4 sektory, ta pętla wykona się 4 razy
                                         }
                                      }
                                    }
                                  if ((Difference + Frame_length) > 4095) //gdy Difference + Frame_length > 4095 (wtedy zdekodowana została ostatnia cała ramka MP3 pamiętana w górnej połowie bufora wejściowego)
                                    { //to teraz złożenie kolejnej ramki MP3 z dwóch fragmentów: pierwszego, pamiętanego na końcu górnej połowy bufora wejściowego
                                     //i drugiego, pamiętanego na początku dolnej połowy bufora wejściowego   
                                      for (i = Difference; i < 4096; i++)
                                           MP3_one_frame[i - Difference] = bufor_we[i]; //wpisanie do tablicy MP3_one_frame pierwszego fragmentu kolejnej ramki MP3
                                      for (i = 4096 - Difference; i < Frame_length; i++)
                                           MP3_one_frame[i] = bufor_we[i - (4096 - Difference)];//wpisanie do tablicy MP3_one_frame drugiego fragmentu tej samej ramki MP3
                                      readPtr = MP3_one_frame;  //teraz wskaźnik readPtr wskazuje pierwszy element tablicy MP3_one_frame
                                      if (! (MP3Decode(hMP3Decoder, &readPtr, &bytesLeft, outBuf, 0)))  //dekodowanie jednej ramki strumienia MP3 z tablicy MP3_one_frame
                                      {  //gdy dekodowanie jednej ramki odbędzie się bez błędu, to:
                                        while (! (AT91C_BASE_SSC->SSC_SR & AT91C_SSC_ENDTX)) AT91F_PIO_ClearOutput( AT91C_BASE_PIOA, AT91C_PIO_PA27);//LED świeci gdy ENDTX=0;   //odczekanie w pętli, aż wszystkie dane z portu SSC pójdą do przetwornika D/A
                                        AT91F_PIO_SetOutput( AT91C_BASE_PIOA, AT91C_PIO_PA27);//LED nie świeci,gdy ENDTX=1
                                        AT91F_SSC_SendFrame( AT91C_BASE_SSC, 0, 0, outBuf, out_Samples); //wysłanie do portu SSC poprzez kanał DMA danych dla przetwornika D/A
                                      }
                                      nFrames++;
                                      readPtr = bufor_we;  //teraz wskaźnik readPtr wskazuje pierwszy element tablicy bufor_we
                                      //a następnie wypełnienie górnej połowy bufora wejściowego kolejnym, następnym blokiem danych odczytanym z karty SD
                                      nBlocks_processed++;
                                      if (licznik < opis_partycji.sector_per_cluster[numer_partycji]) //najpierw spr, czy licznik odczytanych sektorów < liczby sektorów przypadających na jeden klaster pliku
                                      {   //gdy to sprawdzenie jest prawdą: (wtedy nie wszystkie sektory z bieżącego klastra zostały odczytane)
                                         i = 0;  //licznik poniższej pętli
                                         do
                                         {  //to w tej pętli wypełnienie górnej połowy bufora wejściowego
                                           indeks = fill_buffer(start_address, licznik, indeks, rodzaj_karty);
                                           licznik++; //zwiększenie licznika odczytanych sektorów należących do klastra pliku
                                           i++;
                                         }     
                                         while (i < 4); //górna połowa bufora wejściowego mieści 4 sektory, ta pętla wykona się 4 razy
                                      }
                                      else //w przeciwnym razie licznik odczytanych sektorów = liczbie sektorów przypadających na jeden klaster pliku
                                      {    //(czyli zakończyło się odczytywanie bieżącego klastra pliku i trzeba odczytać kolejny klaster zajęty przez odtwarzany plik)
                                         licznik = 0; //wyzerowanie licznika sektorów, bo będzie odczytywany później pierwszy sektor z kolejnego klastra zajętego przez plik
                                         start_address = next_cluster(numer_wpisu_poczatku_listy, rodzaj_karty);//obliczenie adresu pierwszego sektora kolejnego klastra pliku
                                         if (start_address == 0x0FFFFFFF)
                                         {
                                           outOfData = 1;  //gdy start_address == 0x0FFFFFFF (już nie ma więcej klastrów przydzielonych dla wybranego pliku)
                                           break;           //to zakończenie pętli do...while dekodującej ramki MP3
                                         }
                                         else
                                         {  //w przeciwnym razie start_address zawiera adres pierwszego sektora należącego do kolejnego klastra zajętego przez plik i wtedy:
                                           do
                                           { //w tej pętli wypełnienie górnej połowy bufora wejściowego nowymi danymi z kolejnego klastra należącego do pliku
                                             indeks = fill_buffer(start_address, (licznik + 4), indeks, rodzaj_karty);
                                             licznik++; //zwiększenie licznika odczytanych sektorów należących do klastra pliku
                                           }
                                           while (licznik < 4); //górna połowa bufora wejściowego mieści 4 sektory, ta pętla wykona się 4 razy
                                       }
                                      }
                                    }
                                  nFrames++;
                                  Bitrate = mp3FrameInfo.bitrate;
                                }
                              }
            } 
            while (!outOfData);
    Załączniki:
  • Poziom 15  
    Pierwsza myśl, która mi przyszła do głowy gdy zobaczyłem kod, to czy to jest kod testujący działanie dekodera MP3 + DAC? Jeśli tak, to nie dziwie sie że coś nie hula, bo jest dość długi i stosunkowo skomplikowany, przez co może sie w nim kryć potencjalnie dużo błędów, możliwe że malutkich ale dających taki efekt jak słyszysz.
    Proponuje zacząć pisać od początku i małymi kroczkami. Najlepiej uruchomić testwrap z katalogu dekodera troche go przerabiając i odtworzyć na początek dane z pliku mp3.h z katalogu data. Odpada wtedy problem karty SD, uzupełniania bufora dekodera itp. Jako wskaźnik do bufora podajesz dekoderowi tablice mp3data i nie robisz nic więcej jak wysyłanie zdekodowanych danych do SSC. Efektem powinno być odtworzenie około sekundowego dźwięku. Jeśli to przebiegnie bez problemu, możesz wtedy zająć sie odtwarzaniem z karty itp.

    Reasumując, program do prototypu, jak zresztą każdy inny najlepiej pisać małymi kroczkami, co chwila sprawdzając czy wszystko działa (nie przejmuj sie ilością cykli zapisu/odczytu pamięci flash). W razie niepowodzenia wiesz mniej więcej co ostatnio dopisałeś i co może ewentualnie stanowić problem.
  • Poziom 14  
    Dzięki za odp. No, nie jest to testowanie dekodera i DAC, tylko kod mi się tak rozrósł, bo z czasem okazywało się, że trzeba to czy tamto dopisać lub przerobić, by poprawnie odtwarzać pliki np. ze zmiennym bitrate'm, z bitrate'm wiekszym niż 128kb/s, z błędnymi ramkami MP3, czy jeszcze jakieś inne przypadki. Zresztą w komentarzach do kodu opisywałem, po co jest dany fragment kodu. To, co proponujesz, wykonywałem na samym początku uruchamiania dekodera Helix'a, z wgranym do pamięci Flash ARMa plikiem DEMO.MP3 z WinAmp-a (56kb/s, fs=22050Hz) , ale fakt - tylko na DAC-u TDA1545, który ma inny interfejs (nie I2S) i przez to, sądziłem, są te trzaski (bo też były). Potem, w nowym projekcie playerka, zająłem się już obsługą karty SD, wyświetlaczem LCD, obsługą obecnego DAC-a UDA1330 (bo w miedzyczasie go kupiłem), ale nie udało mi się wyeliminować trzasków. Do podstaw już nie wracałem. Spróbuję jednak wrócić do podstaw (może jutro), ale z innym plikiem wgranym do Flasha i z obecnym DAC-em i dam znać, co będzie.
    Możesz mi jeszcze powiedzieć, co to jest ten testwrap z katalogu dekodera? Jest to jakiś plik C?
    Pozdrawiam, KT_priv
  • Poziom 15  
    Testwrap to folder z przykładowym programem, pokazującym jak używać dekodera. Nie wiem skąd ściągałeś źródła dekodera, ale powinieneś takowy folder mieć. Jest on na pewno na stronie Helixa w zakładce datatype>CVS a dokładnie https://helixcommunity.org/viewcvs/datatype/mp3/codec/fixpt/ oraz w paczce z tego postu https://www.elektroda.pl/rtvforum/viewtopic.php?p=2647531#2647531 .

    Sporo z niego można usunąć, np pomiar czasu dekodowania, a zamiast zapisywania danych PCM do pliku, wysyłać je do SSC. Dane MP3 jak wspomniałem wcześniej są w folderze data i są to "czyste" ramki bez ID3 itp. zapisane jako ciągła tablica, więc także funkcja FillReadBuffer nie będzie potrzebna. W tak prostym projekcie łatwiej będzie znaleźć przyczynę trzeszczenia.
  • Poziom 12  
    Czesc,

    Jak tak patrzę na objawy to wydaje mi się, że przyczyną problemów może być to, że źle interpretowane są dane. Chodzi mi o to, że może starsze bajty danych pomylone są z młodszymi i dlatego słychać takie trzaski. Chyba tam gdzieś zostało napisane, że WAV działał, a MP3 nie. Może się mylę, ale ja bym pokombinował z kolejnością bitów/bajtów, które są wysyłane do DAC.

    pozdrawiam,
    genos182
  • Poziom 14  
    OK, dzięki, Genos182:) to też spróbuję. Dziś mam nockę, może trochę posiedzę przy playerku i najpierw uproszczę według podpowiedzi radiody pętlę dekodującą MP3. Dam znać, co wyszło. Wczesniej nie miałem w ogóle czasu usiąść przy tym. Pozdrawiam, KT_priv
  • Poziom 14  
    Witam wszystkich ponownie:) Najnowsze rezultaty moich walk o poprawę dźwięku:
    Pliki WAV odtwarzają się bez problemu przy użyciu kodu radiody z dnia 28.08. Co prawda, pod koniec 3 sek. pliku są jakieś zakłócenia, ale pierwsze ok. 2sek dźwięku są OK. Może to wynika z błędów w kodzie, bo pisałem go na szybciora, a nie miałem później czasu siedzieć nad tym. Pliki MP3 odtwarzają się dobrze, ale tylko te najwolniejsze (56kb/s). Demo z pliku mp3.h też odtwarza się dobrze, ale jest bardzo krótkie. Natomiast odtwarzanie normalnej muzy 128kb/s i więcej tnie się niemiłosiernie. Uzyskałem to wyłączając optymalizację (optimize for size), co zaowocowało znacznym spowolnieniem wykonywania programu, ale za to wszystkie instrukcje kodu wykonywane są po kolei - wiem, bo kiedyś próbowałem debugować z włączoną optymalizacją. Włączenie tej optymalizacji powoduje, że muza odtwarza się normalnie, tyle że trzeszczy.

    Mam pytanie: do czego służy plik polyphase.c? Próba jego dołączenia do projektu kończy się błędami kompilacji (zainkludowałem odpowiednie pliki h do polyphase.c):
    polyphase.c: (.text+0x58): undefined reference to `MADD64'
    polyphase.c: (.text+0x318): undefined reference to `SAR64'
    W tym pliku są funkcje PolyPhaseMono i PolyPhaseStereo. Te same funkcje są w pliku asemblerowym asmpoly_gcc.s. Czy zamiennie używa się tego lub tego pliku zależnie od platformy sprzętowej?

    Do Genos182 - dane wysyłane są dobrze do D/A. Zamiana miejscami bajtów w 16-bit słowie powoduje, że tego się nie da słuchać - straszne szumy i trzaski.

    Konkludując: wnioskuję, że chyba ten mikrokontroler jest za wolny do dekodowania MP3 z wystarczającą jakością - sprawia to konieczność ustawienia 1WS dla zegara 67MHz i konieczność optymalizacji kodu przez kompilator, by uzyskać jego szybsze wykonywanie, ale tracę przez to liniowe jego wykonywanie.
    Może po zmianie platformy sprzętowej na np. jakiś mikrokontroler LPCxxxx lub ARM9 wrócę do softwarowego dekodowania MP3 - ale na razie nie mam odp.platformy. Na obecnie posiadanej płycie ewaluacyjnej mogę uruchamiać tylko mikrokontrolery z serii AT91SAM7S. Póki co noszę się z zamiarem zakupu dekodera STA013 i w ten sposób rozwiązania problemu jakości dźwięku. Do ARM-a sterującego tym STA załaduję jeszcze odtwarzanie WAV-ów (oczywiście wtedy DAC będzie przełączany między ARM-em i STA013 zależnie od odtwarzanego pliku) i jeszcze - czy zajmował się ktoś softwarowym dekodowaniem plików WMA? Czy moc obliczeniowa tego ARM-a starczy do dekodowania WMA, czy też można się spodziewać podobnych problemów, jak z plikami MP3?
    Dzięki za wcześniejsze próby pomocy mi:) Pozdrawiam, KT_priv
  • Poziom 15  
    Plik polyphase.c to filtry polifazowe, potrzebujące podobno najwięcej mocy obliczeniowej, dlatego m.in. zostały napisane także w asemblerze dla gcc, z tad plik asmpoly_gcc.s. Innymi słowy wspomniane przez ciebie pliki robią to samo tyle że polyphase.c jest zdaje sie przeznaczony dla platform 64-bitowych. Korzystaj z polyphase_gcc.s który jest zoptymalizowany dla ARM.

    Dekodowanie software'owe mp3 za pomocą bibliotek Helixa, u mnie zużywa od około 60-90% mocy obliczeniowej, zależnie od utworu, przyczym odtwarzam głównie MP3 128kbps. Zostaje zatem niewiele czasu na inne rzeczy. Wystarczy jednak umieścić kilka krytycznych pod względem czasu wykonywania procedur w pamięci RAM, a wyniki znacznie sie poprawiają. Największą poprawę udało mi sie uzyskać umieszczając właśnie filtry polifazowe w RAM (ponad 2ms krócej na ramkę).

    Mam takie wrażenie że u Ciebie właśnie brakuje czasu procesora dlatego muzyka przy wyższych bitrate'ach się tnie. Polecam na początek zoptymalizować dekoder pod względem wykonywania niektórych procedur z RAM. U mnie najwięcej poprawy przyniosło przeniesienie do RAMu procedur z następujących plików:

    huffman.c (400-700us krócej)
    imdct.c (400-2000us krócej)
    dequant.c dgchan.c (40-800us krócej)
    asmpoly_gcc.s (2000us krócej)

    Sumarycznie udało mi sie doprowadzić do wykorzystania procesora na poziomie 50-70% a wszystko przy taktowaniu 48MHz.

    Sprawdź także szybkość odczytu danych z karty SD, oraz skróć długość bufora danych dla dekodera (READBUF_SIZE) np do 1253 bajtów i dopełniaj go po każdym zdekodowaniu jednej ramki.

    Niestety to nie ARM9 i trzeba sie bić o każde ułemki sekundy, ale w tym chyba cała zabawa.
  • Poziom 14  
    Dzięki:) Powiedz mi jeszcze, jak mam przenieść te procedury (szczególnie asmpoly_gcc.s - największy zysk) do RAM-u?

    Stosuję takie coś (do pierwszej z brzegu funkcji z imdct.c)
    Code:
    static void AntiAlias(int *x, int nBfly) __attribute__ ((section (".data")));
    
    static void AntiAlias(int *x, int nBfly)

    i wywala mi błąd/ostrzeżenie:
    Warning: ignoring changed section attributes for .data

    Jednak, jak patrzę w okienku Project Items w Project Explorer to chyba ta funkcja znalazła się w RAM-ie, bo:
    przedtem :
    imdct.c code: 12524; data: 144
    po zmianie :
    imdct.c code: 10972; data: 1696

    czyli ta funkcja teraz zajmuje w pamieci RAM 1552 bajty.
    Pytanie: czy mam olać to ostrzeżenie, bo jednak się kompiluje i mogę wgrać program do procesora, czy jednak coś z tym ostrzeżeniem robić?

    I w jaki sposób podejść do asmpoly.gcc? Zastosowanie tego samego sposobu, co wyżej daje mi już błąd:
    Error: junk at end of line, first unrecognized character is `_'

    Pozdrawiam,KT_priv
  • Poziom 15  
    Używam kompilatora GCC i tam wygląda to tak:

    Dla funkcji w C
    __attribute__ ((section (".data"))) static void AntiAlias(int *x, int nBfly) ;

    dla funkcji w asm
    .data (na początku pliku asm)

    podobno trzeba też dodać atrybut long_call, ale czy jest on konieczny nie wiem bo nie sprawdzałem, czy działa bez niego. Mogę jedynie powiedzieć, że do funkcji w C go dodaje zawsze, a do asemblerowych nie i też działają.
  • Poziom 14  
    No i przeniosłem te funkcje do RAM-u, rzeczywiście, poprawa jest, odtwarzanie pliku 56kb/s bez zastrzeżeń, pliki 128kb/s jeszcze lekkie zafalowania dźwięku, ale i tak jest lepiej:) Gorzej, że bardzo ubyło RAM-u... a tu jeszcze nie koniec pisania softu, o czym wspomniałem otwierając ten wątek. Znacznie ważniejszym dla mnie jest już zdobyte doświadczenie i wiedza, że ARM7 nie do wszystkich zadań się nadaje. Co do playerka - zrealizuję go na ARM7 i dołożę sprzętowy dekoder MP3, tak, by miał on jeszcze jakąś wartość użytkową (czyt. nadawał się do słuchania muzy nie tylko 128 kb/s :) ) Za jakiś czas pochwalę się tym playerkiem na elektrodzie:) Co do dalszych planów: przyglądam się zestawowi uruchomieniowemu z Propoxu: płyta EVBmmTm i minimoduł MMstr912. Na tym, wydaje mi się, programowe dekodowanie mp3 powinno chodzić bezproblemowo bez "żyłowania" mikrokontrolera, a zdobyte teraz doświadczenie będzie tam przydatne. Niniejszym kończę ten wątek, dziękuję wszystkim, którzy zechcieli udzielić mi pomocnych rad :) Pozdrawiam, KT_priv
  • Poziom 15  
    Chyba za szybko sie poddajesz, ARM7 naprawdę dużo potrafi, tylko trzeba umieć wykorzystać możliwości tego rdzenia, oraz dodatków którymi otoczył go Atmel (jak np. DMA). AT91SAM7S256 spokojnie nadaje sie do dekodowania i odtwarzania plików MP3, mało tego, AAC i OGG także, czego dowodem jest m.in ten projekt http://www.cyfronika.com.pl/kityavt4/avt2887.htm

    Słowem, nie sztuką jest napisać program, który marnotrawi czas procesora i wykorzystuje jakiś znikomy procent jego pozostałych możliwości, a potem wgrać go do monstrum. Z powodzeniem można to samo zrobić na mniejszym uC, pisząc inteligentny program, który wykorzysta każdy MIPS mikrokontrolera.

    Ja z powodzeniem odtwarzam MP3 320kbps i to płynnie, więc nie idź na łatwiznę, bo skoro już siedzisz na ARM to już dawno nie powinny Cie interesować sprzętowe dekodery. Moim zdaniem to trochę tak jak byś cofał się w rozwoju.

    Myśle, że widziałeś na stronie Helixa jaka jest różnica w wykorzystaniu procesora do dekodowania MP3 pomiędzy rdzeniami 7 i 9? To kilka megaherców mniej w przypadku ARM9, tyle że dziewiątkę można taktować naprawdę szybko, więc zostaje więcej czasu za pozostałe operacje. Niestety nieodłącznie idzie za tym zużycie energii co w przenośnych odtwarzaczach MP3 jest kluczową sprawą. Poza tym siódemkę też można trochę przetaktować i w najgorszym przypadku uzyskasz 20 wolnych megaherców na obsługę wszystkiego innego poza dekodowaniem MP3. To chyba dobry wynik, porównywalny do STA013 + jakaś ATmega, raczej na korzyść tej pierwszej opcji.

    Co prawda to tylko zimna kalkulacja nie uwzględniająca pewnych spraw, ale myśle że poprawna jako ocena możliwości.
  • Poziom 14  
    OK, zatem biorę się do pracy. Powiedz mi, czy DMA używasz również do odczytu danych z SD? U mnie odczyt z SD zrealizowany jest w pętli dekodującej mp3 (podałem pełny kod tej pętli w wątku) i wypełnia raz dolną, a raz górną połowę 4kB bufora wejściowego zależnie od tego, z której połowy bufora dane zostały już wykorzystane i można je zastąpić nowymi. Zdaję sobie sprawę, że wypełnianie tych połówek zajmuje czas, bo wykonuje się to u mnie w pętli tak długo, dopóki nie wypełni się połowa bufora. Czy stosowałeś asm, czy całość napisałeś w C? Pozdrawiam, KT_priv
  • Poziom 15  
    Otóż nisko poziomowe procedury odczytu z karty SD są napisane w C i zbudowane są na zasadzie pętli (nie korzystają póki co z DMA) i nie są tak naprawdę jeszcze najszybsze (transfer na poziomie 250kB/s). Korzystając z DMA można spokojnie potroić tą szybkość. Tak jak wcześniej proponowałem, u mnie jest jeden bufor o rozmiarze 1253 (maksymalna wielkość ramki MP3) i dopełniany jest po każdym zdekodowaniu ramki przez nie zmienioną funkcję FillReadBuffer, czyli przenoszącą niewykorzystane dane z końca, na początek bufora i dogrywającą pozostałą część, aż bufor się zapełni. Tu jest ten problem, że nigdy nie wiesz ile dekoder zużyje danych, bo to zależy od wielkości ramki, a przy VBR to w ogóle nic nie wiadomo. Aby dopełnienie trwało jak najkrócej, trzeba albo wywoływać je często korzystając z małego bufora, żeby funkcja memmove nie przewalała za dużo danych, albo korzystać z dużego bufora i dopełniać go gdy zostanie już naprawdę mało danych, ale wtedy zapełnienie takiego bufora znów będzie trwało pewnie za długo jeśli odczyt z karty będzie zbyt wolny, i nie zmieści sie w tym wolnym okienku czasowym. Będzie to słyszalne jako chwilowe przerwanie płynności odtwarzania.

    Obawiam sie, że u Ciebie przy tych sztywnych 2kB buforach może pojawiać się problem z utratą danych. Istnieje bowiem małe prawdopodobieństwo, że dekoder po zdekodowaniu iluś tam ramek zużyje równe 2kB danych. Zwykle zdarza się, że trochę danych w buforze zostanie, ale nie jest to już pełna ramka i musisz je przegrać na początek drugiego bufora zanim go przełączysz. Jeśli tego nie robisz, niektóre ramki są tracone i może to być słyszalne podczas odtwarzania.

    Po przeczytaniu jeszcze raz twojego posta stwierdziłem, że nie do końca go zrozumiałem więc powyższe obawy może nie do końca Cie dotyczą, ale postaraj sie sam przemyśleć ten problem.
  • Poziom 14  
    Witam ponownie
    Wynik najnowszych moich bojów o poprawę dźwięku. Wrzuciłem do Flasha 8 sekundowy kawałek mp3 128kb/s. Mój kod odtwarzający wygląda tak:
    Code:
       
    
    readPtr = b1; //tablica we Flash zawierająca dane tego kawałka mp3
    bytesLeft = sizeof(b1);
    nRead = 0;
    nFrames = 0;
    do
    {
     //find start of next MP3 frame - assume EOF if no sync found /
       offset = MP3FindSyncWord(readPtr, bytesLeft);
       if (offset < 0)
       {
          outOfData = 1;
          break;
       }
       readPtr += offset;
       bytesLeft -= offset;
      // decode one MP3 frame - if offset < 0 then bytesLeft was less than a full frame
       err = MP3Decode(hMP3Decoder, &readPtr, &bytesLeft, outBuf, 0);
       nFrames++;
       if (err)
       {
        // error occurred
       switch (err) {
             case ERR_MP3_INDATA_UNDERFLOW:
                outOfData = 1;
                break;
             case ERR_MP3_MAINDATA_UNDERFLOW:
                // do nothing - next call to decode will provide more mainData
                break;
             case ERR_MP3_FREE_BITRATE_SYNC:
             default:
                outOfData = 1;
                break;
             }
       }
       else
       {
        // no error
        //MP3GetLastFrameInfo(hMP3Decoder, &mp3FrameInfo);  //wywaliłem, by też nie zabierało czasu
         if (nFrames == 1)
         {
           //out_Samples = mp3FrameInfo.outputSamps;
           out_Samples = 2304; //na sztywno przypisałem ilość próbek wyjściowych
           AT91F_SSC_SendFrame( AT91C_BASE_SSC, outBuf, out_Samples, outBuf, out_Samples); //wysłanie do portu SSC poprzez kanał DMA danych dla przetwornika D/A i wypełnienie drugiego bufora DMA
         }
      // while (! (AT91C_BASE_SSC->SSC_SR & AT91C_SSC_ENDTX));  //odczekanie w pętli, aż wszystkie dane z portu SSC pójdą do przetwornika D/A (pierwsza wersja pętli)
         while ( (*AT91C_SSC_TNCR) | (!(*AT91C_SSC_TCR))); //odczekanie w pętli, aż wszystkie dane z portu SSC pójdą do przetwornika D/A (druga wersja pętli)
         AT91F_SSC_SendFrame( AT91C_BASE_SSC, outBuf, out_Samples, outBuf, out_Samples); //wysłanie do portu SSC poprzez kanał DMA danych dla przetwornika D/A
       }
    } while (!outOfData);

    Do RAM-u wrzuciłem:
    asmpoly_gcc.S
    imdct.c (wszystkie funkcje)
    dct32.c (jak wyżej)
    dqchan.c (jak wyżej)
    huffman.c (jak wyżej)
    main.c (w akcie desperacji).
    Procek chodzi mi na 90,3168MHz(!) i 1WS. Zostało mi trochę ponad 2kB wolnego RAM-u. Efekt tych prac: w drugiej wersji pętli oczekującej na wysłanie danych do D/A program się zawiesza (tak skonstruowałem tą pętlę). Powodem jest opróżnienie dwóch buforów DMA i niedopełnienie ich nowymi danymi na czas. W pierwszej wersji tej pętli (oczekiwanie na ENDTX) nadal słychać trzeszczenie. Kompiluję bez optymalizacji kodu.
    Co jeszcze można zoptymalizowac?
    Dla zainteresowanych:
    W CrossWorks przenosząc funkcję do RAM-u trzeba dodać: __attribute__ ((section (".fast"))) [nie ".data"]. Wtedy nie ma tego błędu/ostrzeżenia Warning: ignoring changed section attributes for .data
    Pozdrawiam, KT_priv
  • Pomocny post
    Poziom 15  
    Na początek popraw błędy w kodzie. Ilość zdekodowanych ramek zwiększaj dopiero wtedy gdy nie wystąpi błąd w dekodowaniu, a nie od razu po MP3Decode (to tak na przyszłość). Poza tym gdy zwolni się jeden z buforów (ENDTX = 1), wpisuj nowe wartości tylko do rejestrów następnego bufora DMA, a nie do obu ( AT91F_SSC_SendFrame( AT91C_BASE_SSC, 0, 0, outBuf, out_Samples) ). Zastosuj jednak mimo wszystko pierwszą wersję pętli oczekującej i nie przetaktowuj ARMa, bo to również może stać się źródłem problemów (55MHz na razie w zupełności wystarczy). Jeśli chcesz korzystać z podwójnego buforowania DMA, koniecznie musisz dorobić drugi bufor, bo jak na razie widzę, że operujesz pojedynczym buforem danych wyjściowych i co grosza zapełniasz go gdy DMA z niego korzysta.

    Jeśli to nie pomoże musisz zrobić sobie pomiar czasu wykonywania danych części pętli i takim sposobem dojść co trwa za długo powodując opróżnienie obu buforów.
  • Poziom 14  
    Dzięki za pomoc, radioda :) Poprawiłem kod, uwzględniłem, co proponowałeś i RUSZYŁO !:) Piękny, czysty dźwięk, nawet mp3 320kb/s. Na razie testowałem mp3 odtwarzane z Flasha.
    Okazuje się, że nawet nie trzeba nic wrzucać do RAM-u :) Wszystko chodzi z Flasha.Teraz mam 29kB wolnego RAM-u, a procesor chodzi mi na niecałych 34MHz (!), 0WS i nic się nie tnie ani nie trzeszczy. Ustawienia rejestrów SSC są Atmel'owe. Więcej szczegółów wieczorem, jak wrócę z roboty. Wystawię też kod. Oczywiście przyznaję Ci punkty za pomoc :) Pozdrawiam, KT_priv

    Dodano po 3 [minuty]:

    Acha, byłbym zapomniał. Muszę jednak kompilować z opcją optimize for size dla takiego taktowania procesora. Ale dla mnie jest to nawet lepiej :) Pozdrawiam, KT_priv
  • Poziom 14  
    Witam ponownie. Poniżej obiecany kod funkcji main (dla przypomnienia: odtwarzanie krótkich mptrójek z pamięci Flash mikrokontrolera, zegar 33,8688MHz). Może się komuś przyda :)
    Code:
    int main(void)
    
    {
      int bytesLeft, nRead, err, offset, outOfData, eofReached;
      unsigned char *readPtr;
      MP3FrameInfo mp3FrameInfo;
      MP3Decoder hMP3Decoder;
      int nFrames, out_Samples; // liczba próbek wyjściowych (=1152 lub 2304), odczytana z mp3FrameInfo.outputSamps
      AT91F_MC_EFC_CfgModeReg( AT91C_BASE_MC, (AT91C_MC_FMCN | AT91C_MC_FWS_0FWS)); //ustawienie 0 cyklu WaitState
      config_clk();//konfig zegarów systemowych
      config_IO_port(); //konfig linii IO
      config_I2S();//konfig SSC jako interfejsu I2S
      config_TC1();  //konfig TC1 tak, by wywoływał przerwanie od zrównania się zawartości TC1 z zawartością rejestru TC1_RC
       AT91F_AIC_ConfigureIt( AT91C_BASE_AIC,                      // AIC base address
                              AT91C_ID_TC1,                        // System peripheral ID - licznik/timer TC1
                              AT91C_AIC_PRIOR_LOWEST,             // Min priority
                              AT91C_AIC_SRCTYPE_INT_HIGH_LEVEL,    // Level sensitive, aktywny poziom wysoki
                              compare_tc1_irq_handler);                //nazwa procedury obsługi przerwania pojawiającego się od zrównania się zawartości TC1 z zawartością TC1_RC
       AT91F_AIC_EnableIt(AT91C_BASE_AIC, AT91C_ID_TC1);       //włączenie przerwań od TC1. W tym przerwaniu zrobiłem interfejs L3 do konfiguracji D/A
       libarm_set_irq(1);     //Włączenie przerwań IRQ (0 - wyłączenie przerwań).
       config_DAC(); //konfiguracja przetwornika DAC */
       hMP3Decoder = MP3InitDecoder() ; //konfig dekodera MP3 - alokowanie zmiennych w pamięci
       outOfData = 0;
       eofReached = 0;
       readPtr = b1;  //tablica we Flash zawierająca dane kawałka mp3
       bytesLeft = sizeof(b1);
       nRead = 0;
       nFrames = 0;
       char toggle = 0xFF; //zależnie od wartości tej zmiennej będzie wypełniany outBuf lub next_outBuf w funkcji MP3Decode
                                //gdy w tym samym czasie drugi,wypełniony bufor jest wysyłany do D/A   
       do { //pętla dekodująca i wysyłająca dane do D/A
               //find start of next MP3 frame - assume EOF if no sync found /
          offset = MP3FindSyncWord(readPtr, bytesLeft);
          if (offset < 0) {
             outOfData = 1;
             break;
          }
          readPtr += offset;
          bytesLeft -= offset;
       // decode one MP3 frame - if offset < 0 then bytesLeft was less than a full frame
            if (toggle) err = MP3Decode(hMP3Decoder, &readPtr, &bytesLeft, outBuf, 0);
                  else err = MP3Decode(hMP3Decoder, &readPtr, &bytesLeft, next_outBuf, 0);
           if (err) {
             // error occurred
             switch (err) {
             case ERR_MP3_INDATA_UNDERFLOW:
                outOfData = 1;
                break;
             case ERR_MP3_MAINDATA_UNDERFLOW:
                // do nothing - next call to decode will provide more mainData
                break;
             case ERR_MP3_FREE_BITRATE_SYNC:
             default:
                outOfData = 1;
                break;
             }
          }
                    else
                    { // no error
                       MP3GetLastFrameInfo(hMP3Decoder, &mp3FrameInfo);
                       if (nFrames == 0)
                       {  //teraz wypełnienie obydwóch buforów DMA
                         out_Samples = mp3FrameInfo.outputSamps ;
                         AT91F_SSC_SendFrame( AT91C_BASE_SSC,(char*) outBuf, out_Samples,(char*) outBuf, out_Samples); //wysłanie do portu SSC poprzez kanał DMA danych dla przetwornika D/A i wypełnienie drugiego bufora DMA
                        }
                        while (! (AT91C_BASE_SSC->SSC_SR & AT91C_SSC_ENDTX));  //odczekanie w pętli, aż wszystkie poprzednie dane z portu SSC pójdą do przetwornika D/A
                         if (toggle) AT91F_SSC_SendFrame( AT91C_BASE_SSC, 0,0, (char*) outBuf, out_Samples); //tu wysyłanie do D/A danych z bufora outBuf lub next_outBuf
                         else AT91F_SSC_SendFrame( AT91C_BASE_SSC, 0,0, (char*) next_outBuf, out_Samples);  //zaleznie od wartości zmiennej toggle
                         toggle = ~toggle; //zmiana obecnej wartości toggle na przeciwną
                         nFrames++;
                     }
       } while (!outOfData);

       MP3FreeDecoder(hMP3Decoder);
           
            return 0;
    }


    Spróbowałem też te zmiany wprowadzić w programie mojego playerka i też dały oczekiwany efekt :) Niestety, musiałem jednak zwiększyć zegar procka do 45,1584MHz, bo mptrójki 320kb/s już się cieły. Wolniejsze były odtwarzane ładnie. W najbliższym czasie spróbuję zoptymalizować trochę odczyty z karty SD i wypełnianie buforów wejściowych do dekodera tak, aby zejść z fclk procesora do ok.34MHz (może się uda). Pozdrawiam, KT_priv