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...

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