T*Wave* component + Async=True Keep getting crashes

Please post bug reports, feature requests, or any question regarding the DELPHI AREA projects here.

T*Wave* component + Async=True Keep getting crashes

Postby Weps » November 13th, 2007, 9:26 pm

I'm using your components, which in every aspect are fabulous. I haven't found anything like them on the net, and I looked hard. So of course I'm using them in my applications.

In one of these applications I keep getting crashes either "application defined exception" or "crash in ntdll.dll".

First, I'll explain what my program should do:

The user pressed a key (to cause some action) and the application should play a certain wave file. (nothing new ;)

However, when there is already a sound playing, then that sound should stop playing.

I'm using this code:

Code: Select all
procedure TSnd.PlayTick;
begin
  If Tick.Active Then Begin
    Tick.Stop;
    Tick.WaitForStop;
  End;
  Tick.PlayStock( 0);
end;



This seems to work fine most of the time, but sometimes it crashes, this is the stackcall:

Code: Select all
TWaveAudioOut.InternalClose
TStockAudioPlayer.stop
PlayTick


You can easily reproduce it by just adding a button and put the code above in the onclick. Start and keep pressing enter.

It looks like that the wave component is still receiving and processing messages from the windows audio timer threads (or whatever they are called) even when the wave component says it is stopped and/or has disposed of resources.

Any help would be much appreciated.
Weps
Member
Member
 
Posts: 4
Joined: November 12th, 2007, 4:26 pm

Postby Kambiz » November 14th, 2007, 9:36 am

I'll check the code and inform you about the result.
But it may take a few days because I'm very busy nowadays.
Kambiz
User avatar
Kambiz
Administrator
Administrator
 
Posts: 2429
Joined: March 7th, 2003, 7:10 pm

Postby Kambiz » November 14th, 2007, 12:41 pm

I couldn't regenerate the problem. Please attach a complete code to regenerate the issue, but first make sure that:
  1. You have installed the latest version of the Wave Audio package
  2. You have written thread-safe code for event handlers of the wave player component
Kambiz
User avatar
Kambiz
Administrator
Administrator
 
Posts: 2429
Joined: March 7th, 2003, 7:10 pm

Postby Weps » November 14th, 2007, 3:36 pm

Thanks for looking in to this.

I put source + exe here : http://home.planet.nl/~plum0029/wave.zip

Instructions are in unit1.


Weps
Weps
Member
Member
 
Posts: 4
Joined: November 12th, 2007, 4:26 pm

Postby Kambiz » November 14th, 2007, 8:42 pm

The problem is re-entrance not a bug in the package.
Change your code in this way:

Code: Select all
var
  Entrance: Integer = 0;

procedure TSnd.PlayTick;
begin
  Inc(Entrance);
  if Entrance = 1 then
  begin
    If Tick.Active Then Begin
      Tick.Stop;
      Tick.WaitForStop;
    End;
    Tick.PlayStock( 0);
  end;
  Dec(Entrance);
end;
Kambiz
User avatar
Kambiz
Administrator
Administrator
 
Posts: 2429
Joined: March 7th, 2003, 7:10 pm

Postby Weps » November 14th, 2007, 10:20 pm

To clarify, my example, I just used a short wave file. I've got far longer than those to use.

So, yeah, your solution solves re-entrance, but that way it does not do what I want it to do. This way, it will skip playing any sound until the sound that is being played has finished.

What I want is: that if a sound is being played, it should be stopped and the next sound should start. The sounds should not overlap. To make it 'worse', I can't just use 2 instances and toggle. (because the waves themselves and the number of waves are dynamic)

Anyways, this is what I need:

If (Sound.IsPlaying Then Begin
Sound.Stop;
End;
Sound.LoadOtherWavHere;
Sound.Play;

So, how to do it? Apparently, "Stop" doesn't work as (I think) it should.

I tried myself by putting every StockAudioPlayer.PlayStream in a thread. It works, and solves all the crashes I had.

Code: Select all
constructor TMyPlaySoundThread.Create( aWave: TWaveStorage);
begin
  inherited Create( True);
  S := TStockAudioPlayer.Create( Nil);
  S.Stock := aWave;
  FreeOnTerminate := True;
end;

destructor TMyPlaySoundThread.Destroy;
begin
  S.Free;
  inherited;
end;

procedure TMyPlaySoundThread.Execute;
begin
  S.PlayStock( 0);
  S.WaitForStop;
end;


But with the thread, I've got two other things. One is cpu usage has increased from like 2% to 80% (Async is now off btw) since the thread shouldn't kill itself until the wav has stopped (it sits in WaitForStop...)

And two: I still cannot get the wav to stop immediately, because I simply can't access it... (A Thread.Terminate will still play a few buffers)

So my question stands, how can I stop a wave file being played, either with Async=true in the mainloop
OR Async=false and in a thread?
Weps
Member
Member
 
Posts: 4
Joined: November 12th, 2007, 4:26 pm

Postby Kambiz » November 15th, 2007, 10:50 am

My solution doesn't conflict with your needs. When a wave starts playing, the code has already exited from the PlayTick routine. It only prevents calling WaitForStop more than once.
Kambiz
User avatar
Kambiz
Administrator
Administrator
 
Posts: 2429
Joined: March 7th, 2003, 7:10 pm

Postby Weps » December 3rd, 2007, 7:42 pm

Tested it for some time, and I'm sorry to say, it doesn't help. It might fix re-entry problems, but I'm not sure that's what I've been encountering. (why would one thread re-enter it twice in the first place?)

My example might not have been the best example, it just showed the same error I got all the time. :)

So, as I wrote above, instead of trying to stop every short wave, I just launch them in a seperate thread:

Code: Select all
procedure TSnd.PlayWarnLong;
var
  T : TStockAudioPlayer;
begin
  T := TStockAudioPlayer.Create( Nil);
  T.Async := True;
  T.Stock := WarnLongWave;
  T.OnClose := OnPlayEnd;
  T.PlayStock( 0);
end;


I added the OnClose event to TWaveAudioIO, since OnDeactivate does not tell me (the user) that it is safe to free the TStockAudioPlayer.

OnClose is called from TWaveAudioIO.ProcessMessage:
Code: Select all
      MM_WOM_CLOSE:
      begin
        EnterCriticalSection(CS);
        try
          DoWaveOutDeviceClose;
        finally
          LeaveCriticalSection(CS);
        end;
        If Assigned( FOnClose) Then FOnClose( Self);
      end;


But even this wasn't enough to know whether or not to free the StockAudioPlayer since its thread might (and is!!) still be running in the background...

So I also added

Code: Select all
    // to have thread tell me when I can be freed.
    FThreadTerminated: Boolean;
    // to tell me that I should be freed.
    FDoFree: Boolean;


and changed the thread to:

Code: Select all
destructor TWaveThread.Destroy;
begin
  CloseHandle(WaveAudioIO.ThreadEvent);
  WaveAudioIO.ThreadEvent := 0;

  // Free the WaveAudioIO if asked for.
  // When 'DoFree' is set, it means 'OnClose' was called while this thread
  // was still running. So let the thread free the waveio.
  If WaveAudioIO.DoFree Then Begin
    FreeAndNil( WaveAudioIO);
  End;
  inherited Destroy;
end;


and finally, in the OnClose I do:


Code: Select all
procedure TSnd.OnPlayEnd(Sender: TObject);
var
  s: TStockAudioPlayer;
begin
  s := (Sender as TStockAudioPlayer);

  // Free it if its thread has stopped.
  If S.ThreadTerminated Then Begin
    s.Free;

  // Set the flag to have the thread destroy it.
  End Else Begin
    s.DoFree := True; 
  End;
end;


And this all works fine for ME. No more crashes. (Note that I still have your solution implemented for my longer wavs, but I still have to see it actually trap a re-entry)

Thanks anyways! I still love these components ;)
Weps
Member
Member
 
Posts: 4
Joined: November 12th, 2007, 4:26 pm


Return to DELPHI AREA Projects

Who is online

Users browsing this forum: No registered users and 2 guests