Dekodowanie mp3 w czasie rzeczywistym

0

Witam wszystkich... przejdę od razu do rzeczy.
Piszę program do odtwarzania muzyki i dobrze byłoby wyposażyć program w możliwość odtwarzania mp3, nie tylko wave'ów...

Problem polega na dekodowaniu pliku mp3. Dekodowanie całego pliku zbyt długo trwa, dlatego trzeba zrobić dekodowanie podczas odtwarzania, tzn. dekodowania wybranych fragmentów, które będą aktualnie odtwarzane i tu kwestia jak to zrobić, chciałem użyć LAME do dekodowania, ale nie wiem jak i czy się da dekodować fragmenty pliku, jeśli nie LAME to czym?

Ogólnie wymyśliłem to tak, że zrobię 2 bufory, podczas gdy jeden będzie odtwarzany drugi będzie wypełniany kolejnymi samplami, a w przypadku mp3, chcę jeszcze zdekodować dany fragment mptrójki...

Nie wiem na ile to założenie mi się uda, bo na razie wszystko jest w fazie pomysłu, myślę, że z samym odtwarzaniem wave nie powinienem mieć problemów, mam też koncepcję na listę utworów, aby to mp3 jest problemem

Mam nadzieję, że ktoś przeczyta moje wypociny a tym bardziej coś pomoże... pozdrawiam

0

Ogólnie wymyśliłem to tak, że zrobię 2 bufory, podczas gdy jeden będzie odtwarzany drugi będzie wypełniany kolejnymi samplami, a w przypadku mp3, chcę jeszcze zdekodować dany fragment mptrójki...

No tak to powinno wyglądać.

0

No dobra, na razie nie robię mp3, piszę odtwarzanie wave...
CO do mp3 to znalazłem trochę kodów: http://www.mp3-tech.org/programmer/decoding.html

Co prawda już napisałem, ale jest mały problem, pomiędzy skończeniem odtwarzania pierwszego bufora, a odtworzeniem następnego, następuje malutka przerwa, którą wyraźnie słychać, pewnie to przez kolejkę komunikatów, bo używam CALLBACK_WINDOW, próbowałem z funkcją, ale coś tam w msdn jest, że nie powinno się jej wywoływać, oczywiście następny bufor zaczynam odtwarzać przy komunikacie MM_WOM_DONE

Co do próby z funkcją: normalnie samo napisanie __stdcall powinno powodować, że mogę nazwy funkcji użyć jako wskaźnika(mowa o parametrze waveOutOpen), w tym przypadku nie mogłem, nie chciało się skompilować. Wytoczyłem ciężkie działa ręcznie załadowałem funkcję przez GetProcAddress(), co prawda program się skompilował, ale funkcja nie działa, nie wywołuje się, a po odtworzeniu pierwszego bufora głucho :(

Jakieś pomysły co do wyeliminowania przerwy pomiędzy buforami?

0

Co prawda już napisałem, ale jest mały problem, pomiędzy skończeniem odtwarzania pierwszego bufora, a odtworzeniem następnego, następuje malutka przerwa, którą wyraźnie słychać, pewnie to przez kolejkę komunikatów, bo używam CALLBACK_WINDOW

I tak i nie ;) Podejrzewam, że masz dwa bufory i że drugi bufor wpuszczasz dopiero jak skończy odtwarzać pierwszy, tak?

normalnie samo napisanie __stdcall powinno powodować, że mogę nazwy funkcji użyć jako wskaźnika

__stdcall określa konwencję wywołania, ma się nijak do wskaźników.

Wytoczyłem ciężkie działa ręcznie załadowałem funkcję przez GetProcAddress()

A skąd ty ją ładowałeś, przecież tę funkcję musisz sam zdefiniować? :|

PS. Opcja z funkcją jest trudniejsza do realizacji.

0
0x666 napisał(a)

I tak i nie ;) Podejrzewam, że masz dwa bufory i że drugi bufor wpuszczasz dopiero jak skończy odtwarzać pierwszy, tak?

No niestety nie wiem jak sprawdzić w inny sposób kiedy odtwarzanie buforu sie zakończyło, więc tak...

A skąd ty ją ładowałeś, przecież tę funkcję musisz sam zdefiniować? :|

Tak jest, mam funkcję powiedzmy void CALLBACK waveOutProc(...), i jak podaję jej nazwę jako parametr do waveOutOpen(), kompilator wywala bład i nie wiem czemu, np w procedurze obsługi okna jej nazwa może być wskaźnikiem, który podaję do WNDCLASSEX, i kompilator to toleruje tutaj nie. Gdy zapisze coś takiego:
waveOutOpen(&Device,WAVE_MAPPER,&Format,(DWORD)&waveOutProc,0,CALLBACK_FUNCTION);
zgłasza błąd, że musi zajść konwersja z typu tej funkcji do DWORD,
I właśnie próbowałem uzyskać adres tej funkcji w inny sposób, przez GetProcAddress, tak jakbym ładował funkcję z DLLki podłączonej dynamicznie, tyle, że próbowałem ładować waveOutProc, nie sprawdziłem czy funkcja się poprawnie załadowała, ale chyba zwróciło jej adres, bo jakby mi zwróciło 0, to by wywaliło błąd dostępu, a tak nic się nie działo, nawet pamiętałem o: extern "C" void CALLBACK waveOutProc(...) :D

PS. Opcja z funkcją jest trudniejsza do realizacji.

Aha czyli jednak się da, no to dobrze...

0

No niestety nie wiem jak sprawdzić w inny sposób kiedy odtwarzanie buforu sie zakończyło, więc tak...

Ale zmiana sposobu zwracania buforów nic Ci nie da, jeżeli nie będziesz ich poprawnie dostarczał do karty dźwiękowej. Po pierwsze, bufory muszą być odpowiednio duże (~500ms) i powinno być ich więcej niż 2. Po drugie, jak dostaniesz komunikat MM_WOM_DONE, natychmiast wypełniasz zwrócony bufor danymi i kolejkujesz go funkcją waveOutWrite.

A, zapomniałbym. Odtwarzanie rozpoczynasz od wypełnienia i zakolejkowania wszystkich buforów na raz.

Gdy zapisze coś takiego:
waveOutOpen(&Device,WAVE_MAPPER,&Format,(DWORD)&waveOutProc,0,CALLBACK_FUNCTION);
zgłasza błąd, że musi zajść konwersja z typu tej funkcji do DWORD

U mnie taka forma przechodzi:

waveOutOpen(&Device,WAVE_MAPPER,&Format,(DWORD)waveOutProc,0,CALLBACK_FUNCTION);

Aha czyli jednak się da, no to dobrze...

Da się, ale wewnątrz tej funkcji nie możesz wywoływać funkcji systemowych (poza wymienionymi w manualu), więc musisz zrobić następny wątek, który zajmie się wypełnianiem buforów i puszczaniem ich dalej.

0
0x666 napisał(a)

Ale zmiana sposobu zwracania buforów nic Ci nie da, jeżeli nie będziesz ich poprawnie dostarczał do karty dźwiękowej. Po pierwsze, bufory muszą być odpowiednio duże (~500ms) i powinno być ich więcej niż 2. Po drugie, jak dostaniesz komunikat MM_WOM_DONE, natychmiast wypełniasz zwrócony bufor danymi i kolejkujesz go funkcją waveOutWrite.

A, zapomniałbym. Odtwarzanie rozpoczynasz od wypełnienia i zakolejkowania wszystkich buforów na raz.

hmm, zrobiłem jak mówisz, 3 buffory po 256KB, nadal słychać nierówne zmiany pomiędzy bufforami, tu masz kod, rzuć na niego okiem, może coś poradzisz: http://lublin.webd.pl/crayze/musicplayer.rar

0

3 buffory po 256KB

Zasadniczo, buffory powinny mieć rozmiar, który jest wielokrotnością nBlockAlign lub nChannels*wBitsPerSample>>3.

Zamiast:

if(FileHead.RIFF.RIFF[0]!='R'||FileHead.RIFF.RIFF[1]!='I'||
FileHead.RIFF.RIFF[2]!='F'||FileHead.RIFF.RIFF[3]!='F') return 2;

możesz:

if(memcmp(FileHead.RIFF.RIFF,"RIFF",4))return 2;
//lub
if(strncmp(FileHead.RIFF.RIFF,"RIFF",4))return 2;

Zawsze sprawdzaj, co zawiera WAVEFORMATEX::wFormatTag. Musi być WAVE_FORMAT_PCM lub WAVE_FORMAT_IEEE_FLOAT. Inne formaty na ogół są skompresowane.

 DWORD CopyData[3];
ZeroMemory(&CopyData,12); //<--- pewny jesteś tego '&' ???

O co tu chodzi?

//wysyłanie danych do urządzenia
for(int i=0;i<3;i++) 
	if(CopyData[i])
	{
		Header.lpData=(char*)Buffer[i];
		Header.dwBufferLength=CopyData[i];
		waveOutPrepareHeader(Device,&Header,sizeof(WAVEHDR));
		waveOutWrite(Device,&Header,sizeof(WAVEHDR));
	}
		
	  
//wypełnianie kolejengo(I) bufforu
if(CopyData[2]!=BufferSize)
{
	NumBuffer=-1;
	return 0;
}

if(BufferWrite+BufferSize>FileHead.data.DataSize) CopySize=FileHead.data.DataSize-BufferWrite;
else CopySize=BufferSize;

NumBuffer=0;
ReadFile(hFile,Buffer[NumBuffer],CopySize,&readen,0);
LastBufferSize=CopySize;
BufferWrite+=CopySize;

Dlaczego wypełniasz bufor, który przed chwilą wysłałeś do odtwarzania? I dlaczego jest tylko jedna struktura WAVEHDR? Ma być ich tyle, ile jest buforów.

Podobny błąd:

void Csound_engine::Buffering()
{
   [...]

  Header.lpData=(char*)Buffer[NumBuffer];
  Header.dwBufferLength=LastBufferSize;
  waveOutPrepareHeader(Device,&Header,sizeof(WAVEHDR));
  waveOutWrite(Device,&Header,sizeof(WAVEHDR));

  ReadFile(hFile,Buffer[NumBuffer],CopySize,&readen,0);

   [...]

  NumBuffer++;
  
   [...] 
}

Najpierw wysyłasz, a później wypełniasz - bezsens. Odwrotnie musi być.

Podsumowując, źle czytasz pliki wav (to czysty przypadek, że to działa). Nie wiem jak chcesz czytać inne formaty, jeżeli mieszasz "strumienie" plikowe ze strumieniami audio.

0
0x666 napisał(a)

Zasadniczo, buffory powinny mieć rozmiar, który jest wielokrotnością nBlockAlign lub nChannels*wBitsPerSample>>3.

Ok cenna uwaga...

0x666 napisał(a)

Zamiast:

if(FileHead.RIFF.RIFF[0]!='R'||FileHead.RIFF.RIFF[1]!='I'||
FileHead.RIFF.RIFF[2]!='F'||FileHead.RIFF.RIFF[3]!='F') return 2;

możesz:

if(memcmp(FileHead.RIFF.RIFF,"RIFF",4))return 2;
//lub
if(strncmp(FileHead.RIFF.RIFF,"RIFF",4))return 2;

Oj tam... kwestia zapisu

0x666 napisał(a)

Zawsze sprawdzaj, co zawiera WAVEFORMATEX::wFormatTag. Musi być WAVE_FORMAT_PCM lub WAVE_FORMAT_IEEE_FLOAT. Inne formaty na ogół są skompresowane.

Ok

0x666 napisał(a)
 DWORD CopyData[3];
ZeroMemory(&CopyData,12); //<--- pewny jesteś tego '&' ???

Hehe... tak z przyzwyczajenia jakoś napisałem :D, dobrze że zwróciłeś uwagę, kto wie co by było za tym wskaźnikiem...

0x666 napisał(a)

Dlaczego wypełniasz bufor, który przed chwilą wysłałeś do odtwarzania? I dlaczego jest tylko jedna struktura WAVEHDR? Ma być ich tyle, ile jest buforów.

No rzeczywiście wypełniam bufor, który wysłałem xd
Aha, 3xWAVEHDR to całkiem zmienia postać rzeczy... wiesz moja niewiedza spora, bo przeczytałem tu artykuł o niskopoziomowym odtwarzaniu i próbuję coś tworzyć, jakiś odtwarzacz, co prawda przejrzałem msdn i z grubsza przejrzałem funkcje waveOut..., ale w odtwarzaniu buforami w ogóle nie mam wiedzy, dlatego się pytam xd
Na początku po przeczytaniu artykułu w pierwszej wersji kodu, napisałem to tak: 2 bufory, wypełniam 1, wysyłam, wypełniam drugi, czekam na komunikat zakończenia, wysyłam drugi, wypełniam pierwszy, itd. myślałem, że nie można kolejkować buforów i można wysłać tylko raz waveOutWrite i dopiero po zakończeniu odtwarzania, mogę wysłać następny, już sporo się od ciebie dowiedizałem i wielkie dzięki ci za to...

0x666 napisał(a)

Podsumowując, źle czytasz pliki wav (to czysty przypadek, że to działa). Nie wiem jak chcesz czytać inne formaty, jeżeli mieszasz "strumienie" plikowe ze strumieniami audio.

Jak już powiedziałem nie mam wiedzy na temat plików audio, tyle co przeczytałem z artykułu, w ogóle na razie to skupię się na poprawnym odtwarzaniu, a poprawność ładowanego pliku i obsługa różnych formatów potem, testowy wave się ładuje i odtwarza jakoś, na razie wystarczy, teraz biorę się za poprawianie błędów, podsumowując:

  • robię 3 bufory
  • wypełniam wszystkie
  • wysyłam wszystkie do urządzenia
  • i teraz czekam na komunikat zakończenia odtwarzania pierwszego buforu
  • w nim wypełniam buffor i wysyłam go, a numer buforu teraz wezmę z parametru, więc nie będę musiał ręcznie zapamiętywać, który bufor ostatnio był wypełniony(NumBuffer)</quote>

EDIT**********
Oke kod poprawiony, działa całkiem przyzwoicie, dopóki nic nie robię z oknem, bo już samo ruszanie myszką na nim, powoduje drobniusie przycięcia, ale to zapewne przez zawalenie kolejki komunikatami WM_MOUSEMOVE itp.
Coś wykombinuję, spróbuję z tą funkcją lub coś z kolejką...

0x666 jeszcze jakbyś mógł rzucić raz okiem na kod i sprawdzić czy znowu czegoś nie spie.. źle zrobiłem :-), adres ftp ten sam co w poprzednim poście...

Wielkie dzięki za pomoc, pozdrawiam...

0
0x666 napisał(a)

Da się, ale wewnątrz tej funkcji nie możesz wywoływać funkcji systemowych (poza wymienionymi w manualu), więc musisz zrobić następny wątek, który zajmie się wypełnianiem buforów i puszczaniem ich dalej.

Gdy robię przez procedurę niby wszystko jest ok, ale aplikacja dziwnie się zachowuje przy zamykaniu, np. PostQuitMessage(0); nie działa w ogóle, PostMessage(hwnd,WM_QUIT,0,0); niby działa, ale po wywołaniu tego, proces na kilka sekund nie odpowiada i pewnie winda go wykopuje siłą...
Wspominałeś o nowym wątku, ale jak ma wyglądać jego funkcja, gdzie go tworzyć i jak go informować o przyjściu komunikatu DONE, szczerze mówiąc nie wiem nawet jak się zabrać za ten wątek...

0

Gdy robię przez procedurę niby wszystko jest ok, ale aplikacja dziwnie się zachowuje przy zamykaniu, np. PostQuitMessage(0); nie działa w ogóle, PostMessage(hwnd,WM_QUIT,0,0); niby działa, ale po wywołaniu tego, proces na kilka sekund nie odpowiada i pewnie winda go wykopuje siłą...

Rozumiem, że piszesz o CALLBACK_THREAD i kolejce komunikatów?

Wspominałeś o nowym wątku, ale jak ma wyglądać jego funkcja [...]

Ale ja pisałem o wątku w kontekście CALLBACK_FUNCTION.

0
0x666 napisał(a)

Rozumiem, że piszesz o CALLBACK_THREAD i kolejce komunikatów?

Nie te objawy są przy CALLBACK_FUNCTION, z CALLBACK_THREAD nie próbowałem, teraz umieściłem kod DLL'ce ale to nie robi żadnej różnicy...

0x666 napisał(a)

Ale ja pisałem o wątku w kontekście CALLBACK_FUNCTION.

O to właśnie chodzi, jak to ma wyglądać?

EDIT****
Ok dokładniej przeanalizowałem problem, cały problem tkwi w waveOutReset(); gdy używam funkcji(CALLBACK_FUNCTION) wywołanie Reset wiesza proces, oczywiście przy stosowaniu CALLBACK_WINDOW tego problemu nie ma,
waveOutReset używałem po prostu do zatrzymywania odtwarzania, obejdzie się bez, funkcja zatrzymująca odtwarzanie:

extern "C" void __declspec(dllexport) StopPlaying()
{
if(Device)
{
//waveOutReset(Device); <- z niej trzeba zrezygnować
for(int i=0;i<3;i++) waveOutUnprepareHeader(Device,&Header[i],sizeof(WAVEHDR));
waveOutClose(Device);
Device=0;
}
...
}

0
crayze napisał(a)

Ok dokładniej przeanalizowałem problem, cały problem tkwi w waveOutReset(); gdy używam funkcji(CALLBACK_FUNCTION) wywołanie Reset wiesza proces,

Musiałbyś pokazać obsługę WOM_DONE. Zresztą, w callbacku poza zgłoszeniem gotowości bufora nic innego nie powinieneś robić. Myślę, że lepiej będzie jak zrobisz to z CALLBACK_THREAD. Opcja z funkcją i tak doprowadziłaby Cię do podobnego rozwiązania co opcja z wątkiem tyle, że skomplikowałaby nieco sprawę.

Wątek może wyglądać tak:

MSG   msg;

while(GetMessage( &msg, NULL, 0, 0 )>0)
{ 
   if(msg.message==MM_WOM_DONE)
   {
        HWAVEOUT  hwo=(HWAVEOUT)msg.wParam; 
        WAVEHDR* whdr=(WAVEHDR*)msg.lParam; 
        //i tu cała reszta
   }

   [...]
}
0

Działa bardzo dobrze [browar] wielkie dzięki za pomoc

Jeszcze jedno małe pytanie odnośnie kończenia tego utworzonego wątku, jak zakończyć w nim petlę komunikatów(wysłać WM_QUIT do wątku z poza wątku), tzn obszedłem to, ale wolałbym w naturalny sposób ją zakończyć, wygląda to mniej więcej tak:

Mam funkcję:

DWORD WINAPI ThreadEngine(PVOID pvParam)
{
MSG msg;
while(GetMessage(&msg,0,0,0 ))
{
if(msg.message==MM_WOM_DONE) Buffering(msg.lParam);
if(EndThread) break;
}
return 0;
}

I kończę ją EndThread'em:

void UninitDLLEngine(HINSTANCE hInst)//przy wychodzeniu z biblioteki
{
StopPlaying();
UnloadFile();
EndThread=1;//dopiero tutaj kończę wątek, wolałbym w tym miejscu wysłać komunikat WM_QUIT do wątku, bez używania zmiennej EndThread
}

0
while(GetMessage(&msg,0,0,0 ))
  {
    if(msg.message==MM_WOM_DONE) Buffering(msg.lParam);
    if(EndThread) break;
  }
  return 0;
}

To powinno działać. Wprawdzie GetMessage blokuje pętle do momentu pojawienia się jakiegoś komunikatu, co uniemożliwia natychmiastowe przerwanie pętli przez EndThread, to driver zawsze wysyła MM_WOM_CLOSE przy zamykaniu strumienia audio, więc wszytko powinno być OK.

Zamykanie powinno wyglądać tak:

waveOutReset(Device);
for(int i=0;i<3;i++)
   waveOutUnprepareHeader(Device,&Header[i],sizeof(WAVEHDR));
EndThread=1;   //<--- to najpierw
waveOutClose(Device); //<--- wysyła MM_WOM_CLOSE i powoduje przerwanie wątku

Z drugiej strony, prościej będzie jak przerwiesz pętle w komunikacie MM_WOM_CLOSE ;)

PS. waveOutReset powoduje wywołanie MM_WOM_DONE dla każdego zakolejkowanego bufora, więc powinieneś jakoś zablokować wywołanie funkcji Buffering podczas "resetowania".

0

Ok blokowanie bufforowania po Resecie zrobione:

extern "C" void __declspec(dllexport) StopPlaying()
{
  if(Device)
  {
    LockBuffering=1;//blokujemy buforowanie, odblokowywane jest przy funkcji Play();
    waveOutReset(Device);
    for(int i=0;i<3;i++) waveOutUnprepareHeader(Device,&Header[i],sizeof(WAVEHDR));
    waveOutClose(Device);
    Device=0;
  }
  ...
}

void Buffering(LPARAM lPar)
{
  if(!Device) return;
  if(LockBuffering) return;//blokujemy
  ...
}

Co do kończenia wątku to tak wymyśliłem: będę tworzył wątek tylko na czas odtwarzania, CreateThread będzie wywoływała funkcja Play(); a będę kończył go wraz z końcem odtwarzania, czyli komunikatem MM_WOM_CLOSE

while(GetMessage(&msg,0,0,0 ))
{
  if(msg.message==MM_WOM_DONE) Buffering(msg.lParam);
  if(msg.message==MM_WOM_CLOSE) break;
}

i nie będę potrzebował EndThread

.............
No to chyba wszystko działa jak należy teraz zajmę się kolejnymi rzeczami, możliwością skakania do dowolnego bajta(no może nie tak dowolnego, podzielnego przez (Format.nChannels*Format.wBitsPerSample)>>3) w pliku, wysyłaniem komunikatów po odegraniu buffora, tak aby można było pod silnik podłączyć jakiś pasek postępu odegranego pliku

No i potem rzecz najgorsza, spróbować zrobić dekodowanie mp3

W każdym razie 0x666 dziękuję za pomoc, choć mam przeczucie, że to nie koniec tego wątku :-D

Masz kod jakbyś chciał rzucić okiem: http://lublin.webd.pl/crayze/dllmain.rar

1 użytkowników online, w tym zalogowanych: 0, gości: 1