Błąd w waveOutReset

0

Delphi chyba posiada błąd. Jeżeli ktoś pisał cokolwiek z audio niskopoziomowym (waveIn..., WaveOut...) może też na to trafił. Mianowicie, jeżeli podczas otwierania portu CALLBACK jest ustawiony jako CALLBACK_FUNCTION wywołanie funkcji waveOutReset (lub waveInReset) zawiesza program. Oczywiście wywołanie jest poza procedurą CALLBACK. Dla CALLBACK_WINDOW działa bez pudła. Błąd dotyczy D7 i D2005, innych nie sprawdzałem. Ten sam przykład przeniesiony do C++ - (np. kompilatory MS) działa bez zarzutu. Poradziłem sobie w bardzo dziwny sposób, uruchamiając w procedurze CALLBACK timer, a jego zdarzeniu OnTimer, gdzie interval musi być co najmniej 400-500ms i dopiero wywołanie wave..Reset. I co, ciekawe zrobienie zamiast timera pętli odmierzającej czas (for i := 0 to 500 do begin Application.ProcessMessages; Sleep(1) end;) wcale nie rozwiązuje problemu.

A może jest inne rozwiązanie?

Jacek

0

Funkcja waveOutReset powoduje to, że wszystkie zakolejkowane buffory zostają zwrócone applikacji - więc wysłany zostaje komunikat WOM_DONE dla każdego buffora. Być może za wcześnie usuwasz buffory i stąd ten błąd.

0
0x666 napisał(a)

Funkcja waveOutReset powoduje to, że wszystkie zakolejkowane buffory zostają zwrócone applikacji - więc wysłany zostaje komunikat WOM_DONE dla każdego buffora. Być może za wcześnie usuwasz buffory i stąd ten błąd.

Wiem, o tym. Ale to wygląda na błąd Delphi. W WOM_DONE sprawdzam, czy to ostatni bufor, poza tym, gdybym nawet odczekał 10 s przepuszczając komunikaty to się zawiesi. Natomiast CALLBACK na okno działa poprawnie. Znajomy piszący w C++ przekonwertował kod z Delphi i w Visualu to działa. Prawdopodobnie jest to bug debuggera Delphi i jego zarządzania pamięcią. Jeżeli możesz - sprawdź CALLBACK na funkcję. Może gdzieś popełniłem błąd...

Jacek

0

Ale to wygląda na błąd Delphi

Wiesz, jeżeli wszystko chodzi poza waveXXReset, to nie sądze żeby to było związane z językiem, w którym piszesz.

Jeżeli możesz - sprawdź CALLBACK na funkcję. Może gdzieś popełniłem błąd...

Nie bardzo rozumiem 8-| A gdzie kody??? Z resztą, programuje w C/C++ więc z ich kompilacją i debugowaniem byłby problem ;P Choć spojrzeć mogę ;)

PS. A jak tam nagrywanie MIDI?

0

Od końca... :)

PS. A jak tam nagrywanie MIDI

Chwilowo odpuściłem.

Jeżeli możesz - sprawdź CALLBACK na funkcję. Może gdzieś popełniłem błąd...

Nie bardzo rozumiem 8-|

Otwieram port - CALLBACK_WINDOW (nieistotne parametry):

waveOutOpen(@hwo, cbDevices.ItemIndex - 1, @FWaveFileHeader.FormatData, Handle, 0, CALLBACK_WINDOW)

lub CALLBACK_FUNCTION

waveOutOpen(@hwo, cbDevices.ItemIndex - 1, @FWaveFileHeader.FormatData, DWORD(@waveOutProc), 0, CALLBACK_FUNCTION)

Odbieranie komunikatów przez okno działa prawidłowo (w Close i Open ustawiam tylko FActive odpowiednio na False lub True):

procedure TfmMain.MMWOM_DONE(var Msg : TMessage);
var
  HDR: PWAVEHDR;
  BuffersLeft: Word;
  i: Integer;
begin
  // adres bufora
  HDR := PWAVEHDR(Msg.LParam);
  Log('Callback: odegrano bufor nr: ' + IntToStr(HDR^.dwUser));
  // zwolnienie, przygotowanie i wysłanie nowych danych
  waveOutUnprepareHeader(hwo, HDR, SizeOf(WAVEHDR));
  if not FActive then Exit;
  Preparebuffer(HDR^.dwUser);
  if HDR^.dwBufferLength = 0 then
  // nie wczytano nowych danych
  begin
    BuffersLeft := 0;
    // sprawdzenie ile buforów czeka jeszcze na odegranie
    for i := 0 to BUFFERSCOUNT - 1 do
      BuffersLeft := BuffersLeft + (FBuffer[i].dwFlags and WHDR_INQUEUE);
    if BuffersLeft = 0 then
    // odegrano wszystkie dane
    begin
      Log('Callback: koniec danych');
      ClosePort;
    end;
  end;
end;
procedure waveOutProc(hwo: HWAVEOUT; uMsg: UINT; dwInstance: DWORD; dwParam1: DWORD; dwParam2: DWORD); stdcall;

waveOutProc to w zasadzie to samo - tyle, że HDR := PWAVEHDR(dwParam1). ClosePort to:

procedure TfmMain.ClosePort;
var
  i: Integer;
begin
  if not FActive then Exit;
  Log('Resetowanie portu');
  waveOutReset(hwo);
  FActive := False;
  Log('Zamykanie portu');
  waveOutClose(hwo);
  hwo := 0;
  Log('Zwalnianie pamięci');
  for i := 0 to BUFFERSCOUNT - 1 do
  begin
    FreeMem(FBuffer[i].lpData);
  end;
  FreeAndNil(FWaveStream);
  btnStart.Enabled := True;
  btnStop.Enabled := False;
end;

Nie ma żadnej filozofii. Ewidentnie Delphi ma coś namieszane. Chyba, że masz jakiś pomysł...

Jacek

0

PS. A jak tam nagrywanie MIDI

Chwilowo odpuściłem.

;P

Nie ma żadnej filozofii. Ewidentnie Delphi ma coś namieszane. Chyba, że masz jakiś pomysł...

Ano coś mam ;) Rozumiem, że funkcja (a raczej jej zawartość) TfmMain::MMWOM_DONE jest taka sama jak dla waveOutProc. Nie może/powinno być w niej funkcji ClosePort - pamiętaj, że to jest wątek driver'a, a nie aplikacji.

Dalej:

BuffersLeft := 0;
// sprawdzenie ile buforów czeka jeszcze na odegranie
for i := 0 to BUFFERSCOUNT - 1 do
  BuffersLeft := BuffersLeft + (FBuffer[i].dwFlags and WHDR_INQUEUE);
  if BuffersLeft = 0 then
  // odegrano wszystkie dane
  begin
      Log('Callback: koniec danych');
      ClosePort;
  end;

ta część jest troche bez sensu ;) wystarczy zrobić jedną globalną zmienną np.: g_dont_send_buffers, która przed wywołaniem waveOutReset zostanie ustawiona na true co spowoduje, że handler WOM_DONE nie wyśle z powrotem buffora do driver'a audio. W takim układzie, handler WOM_DONE odbierze wszystkie zalegające buffory ale nic z nimi nie zrobi. Mówiąc o wywołaniu waveOutReset miałem na myśli wywołanie w wątku głównym aplikacji. Aha, po wyresetowaniu driver'a sprawdzasz w pętli stan flagi dwFlags wszystkich bufforów. Czyli podobnie jak u ciebie tyle, że ta pętla kończy się gdy wszystkie buffory mają stan WHDR_DONE. Dodatkowo w pętle wstaw Sleep(200) co by proca nie obciążać.

Następna sprawa. Nie musisz przed każdym wysłaniem bufora używać waveOutUnprepareHeader i waveOutPrepareHeader. Wystarczy raz przy tworzeniu danego buffora.

To tyle ;)

0

Nie musisz przed każdym wysłaniem bufora używać waveOutUnprepareHeader i waveOutPrepareHeader. Wystarczy raz przy tworzeniu danego buffora.

No widzisz... A praktycznie we wszystkich przykładach, któe do tej pory namierzyłem tak robili... Zrobiłem jak radzisz i też gra ;) Dzięki.

Rozumiem, że funkcja (a raczej jej zawartość) TfmMain::MMWOM_DONE jest taka sama jak dla waveOutProc. Nie może/powinno być w niej funkcji ClosePort - pamiętaj, że to jest wątek driver'a, a nie aplikacji.

Nie kumam trochę..

wystarczy zrobić jedną globalną zmienną np.: g_dont_send_buffers, która przed wywołaniem waveOutReset zostanie ustawiona na true co spowoduje, że handler WOM_DONE nie wyśle z powrotem buffora do driver'a audio.

No to mam prośbę o łatwiejszą wersję ;)

Otwieram port z CALLBACK_FUNCTION, inicjalizuję nagłówki + waveOutPrepare. Robię waveOutPause; wczytuję dane do buforów, zaczynam odtwarzanie. Zagrał pierwszy bufor, dostaję callback z WOM_DONE; w WOM_DONE sprawdzam, czy aktywny jest port - jeśli nie to wynocha stąd :) Biorę adres nagłówka i wrzucam mu kolejną porcję danych, jeśli danych jest więcej niż zero to robię waveOutWrite. I dalej zima. Nie wiem, jak to rozwiązać. Chodzi mi o zakończenie odtwarzania - jak to teraz sprawdzić, skoro mój sposób jest zły? Jak ustawię tę zmienną DontSendBuffers na true, kiedy plik się skończył to nic mi to nie da, bo muszę jeszcze odegrać te bufory, które czekają w kolejce - więc muszę wykonać to sprawdzanie... Przynajmniej tak to zrozumiałem... Inna sytuacja - rozmiar bufora jest większy niż wielkość pliku - jak wtedy postąpić? No i jak wywołać to waveOutReset, skoro nie mogę ze środka funkcji CALLBACK? Jedyne co mi się udało to uruchomić timer, który chwilę później sam wywoła ten Reset. Komunikatem? Nie mam pomysłu. Jeżeli masz cierpliwość napisz mi schematycznie jak to rozwiązać...

Jacek

0

Nie kumam trochę..

Chodziło mi o to, że twój problem polega na użyciu callback'a waveOutProc, a w kodach nie podałeś jego implementacji więc założyłem, że jest podobny (lub identyczny) do handlera TfmMain::MMWOM_DONE. I jeżeli tak jest to jestem na 90% pewny, że wywołanie ClosePort z callback'a powoduje ten błąd, o którym mówisz ;) Dlaczego??? Ano dlatego, że ów funkcja wywołuje waveOutClose(hwo); co powoduje, że wątek wywołujący (czyli ten, który wywołał waveOutProc)zostaje zamknięty. W wersji z oknem (MM_WOM_DATA) sprawa była prosta - wszystko odbywało się w jednym, głównym wątku - wszystko działo się tak jak to sobie zaprogramowałeś. No ale tu masz dwa wątki więc dochodzą pewne ograniczenia w tworzeniu pewnych mechanizmów.

No to mam prośbę o łatwiejszą wersję

PLAY (wątek aplikacji)

  1. Otwierasz port
  2. preparujesz N buforów
  3. ustawiasz g_dont_send_buffers na false
  4. w zależności o trybu pracy możesz je w tym miejscu wypełnić
  5. kolejkujesz N buforów funkcją waveOutWrite (początek odtwarzania)

STOP (wątek aplikacji)

  1. ustawiasz g_dont_send_buffers na true
  2. wywołujesz waveOutReset
  3. w pętli czekasz aż wszystkie buffory mają status WHDR_DONE (pamiętaj o Sleep(200) :P). Możesz także sprawdzać WHDR_INQUEUE ale ważne jest żeby sprawdzanie odbywało się dotąd aż wszystkie bufory będą wolne.
  4. waveOutUnprepareHeader
  5. zwalniasz pamięć buforów.
  6. zamykasz port

WOM_DONE (wątek driver'a)

  1. sprawdzasz g_dont_send_buffers
  2. jeżeli g_dont_send_buffers jest false wypełniasz bufor i kolejkujesz waveOutWrite
  3. jeżeli g_dont_send_buffers jest true nie robisz nic

Chodzi mi o zakończenie odtwarzania - jak to teraz sprawdzić, skoro mój sposób jest zły? Jak ustawię tę zmienną DontSendBuffers na true, kiedy plik się skończył to nic mi to nie da, bo muszę jeszcze odegrać te bufory, które czekają w kolejce - więc muszę wykonać to sprawdzanie...

Jeżeli strumień (np. pliku audio) jest już pusty to ustawiasz g_dont_send_buffers na true ;) Effekt: patrz WOM_DONE pt.3.

Inna sytuacja - rozmiar bufora jest większy niż wielkość pliku - jak wtedy postąpić?

Z tą wielkością buforów to nie przesadzaj ;P 4x100ms powinno wystarczyć (choć ilość i rozmiar buforów lepiej zrobić regulowaną). No ale jeżeli wystąpi taka sytuacja to wtedy nadmiar danego bufora zerujesz (w sensie wartości sampla). Tutaj możesz już przyjąć, że strumień się skończył więc ustawiasz g_dont_send_buffers na true.

No i jak wywołać to waveOutReset, skoro nie mogę ze środka funkcji CALLBACK? Jedyne co mi się udało to uruchomić timer, który chwilę później sam wywoła ten Reset. Komunikatem? Nie mam pomysłu.

W swoim projekcie zastosowałem timer (ten okienkowy WM_TIMER, 50ms), który sprawdza pośrednio stan g_dont_send_buffers. I jeśli true, wywołuje funkcje STOP (ta u góry). Dodatkowo ten timer obsługuje VU-metry, kursor (ten od pozycji odtwarzania) i inne rzeczy związane z GUI.

//DODANE
Fuck, zapomniałem o zliczaniu bufforów... ale o tym to jutro ;P

Uffff....

0

Poradziłem sobie. Nieco inaczej niż proponowałeś. W Callback mam tak:

MM_WOM_DONE:
      begin
        if not fmMain.FActive then Exit;
        HDR := PWAVEHDR(dwParam1);
        fmMain.Log('Callback: odegrano bufor nr: ' + IntToStr(HDR^.dwUser));
        fmMain.Preparebuffer(HDR^.dwUser);
        if HDR^.dwBufferLength = 0 then
        begin
          BuffersLeft := 0;
          for i := 0 to BUFFERSCOUNT - 1 do
          begin
            BuffersLeft := BuffersLeft + (fmMain.FBuffer[i].dwFlags and WHDR_INQUEUE);
          end;
          if BuffersLeft = 0 then
          begin
            fmMain.Log('Callback: Finished');
            PostMessage(fmMain.Handle, WM_FINISHED, 0, 0);
          end;
        end;
      end;

PrepareBuffer:

procedure TfmMain.PrepareBuffer(i: Integer);
begin
  if not FStop then
    FBuffer[i].dwBufferLength := FWaveStream.Read(FBuffer[i].lpData^, SIZEOFBUFFER)
  else begin
    FBuffer[i].lpData := nil;
    FBuffer[i].dwBufferLength := 0;
  end;
  if (FBuffer[i].dwBufferLength > 0) then
  begin
    MMR(waveOutWrite(hwo, @FBuffer[i], SizeOf(WAVEHDR)));
    if FBuffer[i].dwBufferLength < SIZEOFBUFFER then
    begin
      if chbLoop.Checked then
        FWaveStream.Position := FDataStart;
    end;
  end;
end;

Po odebraniu wiadomości robię reset, unprepare i close. Wciśnięcie Stop to tylko FStop := True.

Pozostał jeden problem. Z odtwarzaniem przez kartę wbudowaną w płytę nie ma problemu. Natomiast próba odegrania przez "poważną" kartę (RME HDSP Multiface II) kończy się zagraniem jednego bufora i dalej wisi. I jak poprzednio - CALLBACK na okno działa poprawnie, na funkcję nie. Otwieranie portu z parametrem WAVE_MAPPED czy WAVE_ALLOWSYNC nie pomaga. Jakiś pomysł? :)

Jacek

0

Natomiast próba odegrania przez "poważną" kartę (RME HDSP Multiface II) kończy się zagraniem jednego bufora i dalej wisi.

Wisi czyli co??? zawiesza się czy po prostu nie odtwarza???

Czy to przypadkiem nie powoduje wycieków pamięci???:

...
FBuffer[i].lpData := nil;
...

Po co TfmMain.PrepareBuffer(i: Integer); jako parametr bierze index bufora jeżeli WOM_DONE zwraca wskaźnik (dwParam1) na zwracany WAVEHDR. To nie błąd ale niepotrzebny zabieg ;)

0
0x666 napisał(a)

Natomiast próba odegrania przez "poważną" kartę (RME HDSP Multiface II) kończy się zagraniem jednego bufora i dalej wisi.

Wisi czyli co??? zawiesza się czy po prostu nie odtwarza???

Nie odtwarza, aplikacja działa. I w tym wypadku waveOutReset oczywiście zawiesza program.

Czy to przypadkiem nie powoduje wycieków pamięci???:

...
FBuffer[i].lpData := nil;
...

No właśnie, jak tego nie ma, to wydaje mam wrażenie, że są wycieki... Uważasz, że to zbędne, skoro później robię FreeMem dla tego bufora?

Po co TfmMain.PrepareBuffer(i: Integer); jako parametr bierze index bufora jeżeli WOM_DONE zwraca wskaźnik (dwParam1) na zwracany WAVEHDR. To nie błąd ale niepotrzebny zabieg ;)

A jaka jest różnica w działaniu, jeżeli podam wskaźnik do bufora, a nie jego numer, skoro struktury WAVEHDR są w w ponumerowanej tablicy? Czy chodzi tylko o "estetykę"? :)

Kupiłem taką "mądrą" książkę - Win32 Multimedia API (Alan C. Moore). No i zawiodłem się - zerżnięte żywcem z helpa, przykład zawiesza się:) I pisane mechanicznie - np. jest opis funkcji waveOutStop... waveOutAddBuffer... Generalnie wtopa za 30 USD. A mam tyle wątpliwości. Nie bardzo umiem wykorzystać Eventy, a spotkałem się z takimi przykładami w C++, gdzie zamiast Application.ProcessMessages w pętli po takim waveOutReset czeka się na Event... Bardzo żałuję, że w ogóle "wdepnąłem" w Delphi. Fajne, ale 95% przykładów, jak nie więcej, dla audio czy MIDI jest w C++. Bawię się zupełnie hobbystycznie, dla tej zabawy rok temu kupiłem D2005 Pro i teraz myślę, że to był błąd, żę chyba lepiej byłoby przemęczyć się i zainwestować w VS. Nie to, że mi się Delphi nie podoba, tylko, że rejony związane z muzyką - czyli moją branżą - zdecydowanie łatwiej programuje się w C++, albo tak mi się wydaje... :D Wracając do książki, myślałem, że dowiem się jakie są różnice (sens) w Callback do funkcji, okna, zdarzenia, wątku. A tu nadal jestem w lesie ;-) Polskich publikacji brak. A dla Delphi - wszem, TMediaPlayer lub PlaySound... :) Szukam korepetytora :D

Jacek

0

I w tym wypadku waveOutReset oczywiście zawiesza program.

No to w tym momencie trochę się pogubiłem ;) Czyli na karcie wbudowanej z callback'iem wszystko chodzi, tak???

No właśnie, jak tego nie ma, to wydaje mam wrażenie, że są wycieki... Uważasz, że to zbędne, skoro później robię FreeMem dla tego bufora?

No ale jak??? Jeżeli wyzerujesz wskaźnik FBuffer[0].lpData, a później dasz FreeMem(FBuffer[0].lpData); to de facto nic nie zwalniasz ;) no chyba, że wskaźniki przechowujesz gdzieś, w innej tablicy (choć to bez sensu). Poza tym bufory powinieneś zwalniać po odpreparowaniu funkcją waveOutUnprepareHeader.

Czy chodzi tylko o "estetykę"?

Między innymi tak ale:

twoja wersja:

HDR := PWAVEHDR(Msg.LParam);
...
i (parametr funkcji):=HDR^.dwUser
... 
FBuffer[i].lpData   //<--- niepotrzebne obliczanie offsetu i*sizeof(WAVEHDR)

wersja gdzie PrepareBuffer zamiast indexu bierze adres:

lpBuffer (parametr funkcji):=Msg.LParam
...
lpBuffer^.lpData

czyli z dwóch przypisań i obliczania offsetu mamy jedno przypisanie ;) To są drobiazgi ale jak takich drobiazgów zrobi się kilkadziesiąt/kilkaset w miescu gdzie wymagana jest wysoka wydajność to... sam rozumiesz ;) W przypadku twojego kodu to raczej chodziło mi o estetykę - tak mi się rzuciło w oczy ;P

Nie to, że mi się Delphi nie podoba, tylko, że rejony związane z muzyką - czyli moją branżą - zdecydowanie łatwiej programuje się w C++, albo tak mi się wydaje...

No nie sądze. Bez poznania mechanizmów związanych w obsługą audio (choć nie tylko) to i C/C++ nie pomoże ;) Wiesz, ja przez to wszystko przechodziłem, włączając twój problem ale z czasem wszystko stało się w miarę przejrzyste. W miarę, bo cały czas poznaje różne techniki audio.

Wracając do książki, myślałem, że dowiem się jakie są różnice (sens) w Callback do funkcji, okna, zdarzenia, wątku. A tu nadal jestem w lesie

W skrócie:

Callback: oddzielny wątek; funkcja wywoływana przy otwieraniu, zamykaniu i zwracaniu bufora. W sam raz gdy odtwarzany dzwięk jest jeszcze obrabiany np: przez efekty. Minus - konieczność synchronizacji z wątkiem applikacji.

Thread: idea podobna do wysyłania komunikatów do okna tyle, że te komunikaty wysyłane są do message loop, którą trzeba samemu zrobić. Całą tą pętle umieszczasz w oddzielnym wątku stworzonym funkcją CreateThread. Plusy i minusy jak w opcji z callbackiem. Prawdopodobnie łatwiej zrobić przekierowanie wejścia do wyjścia audio (np. processor effektów choć na waveOut i waveIn to absurd ;P)

Komunikaty okienkowe Raczej nie nadaje się tam gdzie dźwięk jest intensywnie przetwarzany efektami - może blokować aplikacje. Do prostego odtwarzania audio.

Event idea prosta. Przed zakolejkowaniem bufora ustawiasz event na stan nonsignaled. W chwili gdy driver skończy z bufforem zmieni stan na signaled. Funkcją WaitForSingleObject sprawdzasz jaki jest stan event'a. Z tego co zaobserwowałem, to rzadko jest to stosowane.

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