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

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

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

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

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

Kambiz

Posts: 2430
Joined: March 7th, 2003, 7:10 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

Kambiz

Posts: 2430
Joined: March 7th, 2003, 7:10 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

Posts: 4
Joined: November 12th, 2007, 4:26 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

Kambiz

Posts: 2430
Joined: March 7th, 2003, 7:10 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.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

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

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

Kambiz

Posts: 2430
Joined: March 7th, 2003, 7:10 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...

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;

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

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