• Welcome to Jose's Read Only Forum 2023.
 

Trouble with threads and sound

Started by Petr Schreiber, July 02, 2010, 11:13:20 PM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

Petr Schreiber

Hi,

I just recently discovered I cannot use MCI in applications with threads. I guess the problem could be in MCI itself, but I am curious what sound library (DirectSound?) to pick for multithreaded application, where I might open sound in one thread, and want to close it in another.

Here is the "failing" example:

#COMPILE EXE
#DIM ALL

#INCLUDE "win32api.inc"
THREAD FUNCTION Stopper(BYVAL Dummy AS LONG) AS LONG
 #DEBUG PRINT "THREAD BEGAN, PLAY FOR 2 SECONDS"
 SLEEP 2000
 ' -- MySound alias not recognized in THREAD
 mciSendString("stop MySound", BYVAL %NULL, 0, BYVAL %NULL)
 mciSendString("close MySound", BYVAL %NULL, 0, BYVAL %NULL)
 FUNCTION = 123
 #DEBUG PRINT "THREAD ENDS"
END FUNCTION

FUNCTION PBMAIN () AS LONG

 mciSendString("open C:\SomeLongerSound.wav alias MySound", BYVAL %NULL, 0, BYVAL %NULL)
 mciSendString("play MySound", BYVAL %NULL, 0, BYVAL %NULL)
 
 LOCAL hThread AS DWORD
 THREAD CREATE Stopper(1) TO hThread
 
 LOCAL s AS LONG
 
 WHILE s <> 123
   THREAD STATUS hThread TO s
 WEND
 THREAD CLOSE hThread TO s
 
 #DEBUG PRINT "THREAD FINISHED, WAITING TO CHECK IF SOUND STILL PLAYS"
 SLEEP 5000

END FUNCTION


Any ideas would be highly appreciated!
The only thing I need to do is to:

  • Play wav sound asynchronously
  • Be able to interrupt the playing and start playing other thing
  • Have it thread safe (thats what I understand as ability to open sound in one thread and close within another)
  • Be able to check whether sound has ended playing

Thanks,
Petr
AMD Sempron 3400+ | 1GB RAM @ 533MHz | GeForce 6200 / GeForce 9500GT | 32bit Windows XP SP3

psch.thinbasic.com

Jürgen Huhn

Hi Peter,

i missing the declaration`s:

DECLARE FUNCTION mciGetDeviceID LIB "WINMM.DLL" ALIAS "mciGetDeviceIDA" (lpstrName AS ASCIIZ) AS LONG
DECLARE FUNCTION mciGetErrorString LIB "WINMM.DLL" ALIAS "mciGetErrorStringA" (BYVAL dwError AS DWORD, lpstrBuffer AS ASCIIZ, BYVAL uLength AS DWORD) AS LONG
DECLARE FUNCTION mciSendString LIB "WINMM.DLL" ALIAS "mciSendStringA" (lpstrCommand AS ASCIIZ, lpstrReturnString AS ASCIIZ, BYVAL uReturnLength AS DWORD, BYVAL hwndCallback AS DWORD) AS LONG

Added the two other`s... Usefull to check some issues!
Where do you have declared them??
:-X
...
.¸.•'´¯)¸.•'´¯)¸.•'´¯)¸.•'´¯)
¤ª"˜¨¨¯¯¨¨˜"ª¤....¤ ª"˜¨

José Roca

Quote
Where do you have declared them??

They're are ready if you use my include files. They're located in MMSystem.inc, that is included when you include windows.inc or win32api.inc, unless you define the constant WIN32_LEAN_AND_MEAN before including windows.inc.

Jürgen Huhn

#3
Quote#INCLUDE "win32api.inc"

Yes, thank`s, i dom`t know why i didn`t recogbized the inlude. :o
Thank you for replay!
:)
..
.¸.•'´¯)¸.•'´¯)¸.•'´¯)¸.•'´¯)
¤ª"˜¨¨¯¯¨¨˜"ª¤....¤ ª"˜¨

Jürgen Huhn

#4
Maybe it`s helpful to crate all in two Thread`s and  you`re  be able to  identfy every Thread...

Something like this:
#COMPILE EXE
#DIM ALL

#INCLUDE "win32api.inc"
THREAD FUNCTION Starter(BYVAL Dummy AS LONG) AS LONG
 #DEBUG PRINT "THREAD BEGAN, PLAY FOR 2 SECONDS"
 SLEEP 2000
 ' -- MySound alias not recognized in THREAD
 mciSendString("open C:\SomeLongerSound.wav alias MySound", BYVAL %NULL, 0, BYVAL %NULL)
 mciSendString("play MySound", BYVAL %NULL, 0, BYVAL %NULL)
 FUNCTION = Dummy
 #DEBUG PRINT "THREAD ENDS"
END FUNCTION

THREAD FUNCTION Stopper(BYVAL Dummy AS LONG) AS LONG
 #DEBUG PRINT "THREAD BEGAN, PLAY FOR 2 SECONDS"
 SLEEP 2000
 ' -- MySound alias not recognized in THREAD
 mciSendString("stop MySound", BYVAL %NULL, 0, BYVAL %NULL)
 mciSendString("close MySound", BYVAL %NULL, 0, BYVAL %NULL)
 FUNCTION = Dummy
 #DEBUG PRINT "THREAD ENDS"
END FUNCTION

FUNCTION PBMAIN () AS LONG

 DIM hThread(1 to 2) AS LOCAL DWORD

 THREAD CREATE Starter(1) TO hThread(1)
 msgbox  "Thread 1 Started! "
 
 THREAD CREATE Stopper(1) TO hThread(2)
 msgbox  "Thread 2 Started! " + $crlf + _
         "Click -OK-  and wait for them to finish!"

 LOCAL s AS LONG
 LOCAL x AS LONG

  DO
   FOR x = 1 TO 2
     SLEEP 0
     THREAD STATUS hThread(x) TO s
     IF s <> &H103 AND s <> 1 THEN ITERATE DO
   NEXT
 LOOP WHILE s

 FOR x = 1 TO 2
   THREAD CLOSE hThread(x) TO s
 NEXT x

 msgbox "Finished!"
 #DEBUG PRINT "THREAD FINISHED, WAITING TO CHECK IF SOUND STILL PLAYS"
 SLEEP 5000

END FUNCTION


Nothing tested, it`s only a suggestion!
...

;)
.¸.•'´¯)¸.•'´¯)¸.•'´¯)¸.•'´¯)
¤ª"˜¨¨¯¯¨¨˜"ª¤....¤ ª"˜¨

Petr Schreiber

Thanks Jürgen,

I think the main problem is that MCI is probably not thread safe.
I am starting to be crazy, I am solving simple problem of starting sound in one thread and finishing it in other for more than week now, but without success.

I think I should leave MCI and look for DirectSound or some other alternative, but not sure which.


Thank you,
Petr
AMD Sempron 3400+ | 1GB RAM @ 533MHz | GeForce 6200 / GeForce 9500GT | 32bit Windows XP SP3

psch.thinbasic.com

Patrice Terrier

#6
Petr,

While i have allways considered MCI as a tool (compared to DirectSound), there is however a solution that would let you use it 100% thread safe.
This is the same technic i am using in my GDImage HUDplus project, where the second thread is indead a distinct exe, running under control of another one.

To perform this i am registering my own private Windows message, using:
QuoteWM_ZWPNOTIFICATION = RegisterWindowMessage("ZWPnotification")

then i am using the SendMessage API to communicate between the two process, this way:
   CASE WM_ZWPNOTIFICATION
        SELECT CASE LONG wParam
        CASE %NM_AUDIOCOMPLETION
'           // Uncheck VisualPlugin
           CALL SendMessage(GetDlgItem(hWnd, %IDC_MAIN_PLUGIN), %BM_SETCHECK, %BST_UNCHECKED, 0)
           ZWP_Hide()
           hPopup = skGetDWMregion(hWnd)
           IF hPopup THEN
              CALL SetWindowPos(hPopup, GetWindow(hWnd, 2), 0, 0, 0, 0, %SWP_NOMOVE OR %SWP_NOSIZE OR %SWP_NOOWNERZORDER OR %SWP_NOACTIVATE OR %SWP_SHOWWINDOW)
              CALL SetFocus(hWnd)
           END IF
        END SELECT


...
Patrice Terrier
GDImage (advanced graphic addon)
http://www.zapsolution.com

José Roca

Petr,

Threading in COM is a complex subject. I never have used it. Each thread uses its own instance of the COM library and object references have to be marshaled.

See: http://msdn.microsoft.com/en-us/library/ms693344%28VS.85%29.aspx

Petr Schreiber

#8
Thank you very much for the replies,

I will try Patrice's approach in the mean time (it would never occur to me, thanks for the tip!), and look to knowledge source from José.
I would like to have this working from within DLL written in PB, called from .NET application, which creates the threads.
Normally I would do it all in PB, but I am working in team where .NET is the main platform.

It is very funny how simple task can become big problem  ;D / :'(


Petr
AMD Sempron 3400+ | 1GB RAM @ 533MHz | GeForce 6200 / GeForce 9500GT | 32bit Windows XP SP3

psch.thinbasic.com

Petr Schreiber

Hi guys,

it seems I found solution, you will probably laugh, but it works.

- I use MCI to just open file to get its length in milliseconds and then I immediately close it
- I setup timer, with interval equivalent to sound length
- I use PlaySound API to stop/play the sound
- I use waveOutGetVolume/waveOutSetVolume to manipulate volume (I get the volume before app starts and set it back once app ends)

It is rough, but seems to work without single problem :)


Petr
AMD Sempron 3400+ | 1GB RAM @ 533MHz | GeForce 6200 / GeForce 9500GT | 32bit Windows XP SP3

psch.thinbasic.com

Jürgen Huhn

Peter,

that`s really funny and also tricky..   :D

But if you identify every Thread and open MySound in a Thread then it`s possible to close MySound
from same Thread ID.

I`ve modified the Code to create 4 Threads.
You`re be able to stop playing any Thread you want! If no Thread stopped by User the Threads running until they finished
and Program end and exit.

Here is all in the .zip:
.¸.•'´¯)¸.•'´¯)¸.•'´¯)¸.•'´¯)
¤ª"˜¨¨¯¯¨¨˜"ª¤....¤ ª"˜¨

Petr Schreiber

Thanks Jürgen!,

your code worked nicely here!

But sadly it would not work for my case.

The program I am collaborating on has one "main" thread, which creates ~global instance of object for playing sounds.
Then the application has multiple threads on its own, in each it is possible to play sound via the object. But - at any moment, I need just one sound to be played.

So when Thread #1 starts playing, and Thread #2 needs to broadcast, the Thread #2 needs to first stop the sound started in Thread #1. But MCI did not recognized the alias created in #1 when looking at it from #2. And it is sadly not just 2 threads, but many.

That is why PlaySound worked well here, with MCI I sometimes got 2 sounds playing at the same time (same alias, but as they were in different threads they could not stop the sound played), PlaySound is so "primitive" it does not result in such a situations.

Threading is interesting topic, I am still reading through the material José posted.


Petr
AMD Sempron 3400+ | 1GB RAM @ 533MHz | GeForce 6200 / GeForce 9500GT | 32bit Windows XP SP3

psch.thinbasic.com

Jürgen Huhn

Good morning and thank's for reply Petr!

It works sadly not for your case, but some simple examole Codes are always helping
to find the right Solution..

I prefer also the WinApi Syntax, because there you have a large Pool of Functions wich
probably working with different Compiler or third party library's.
Your code was written in PB Syntax "Thread Create", that`s why i did it also in PB Style.
Using "CreateThread" provides more Parameter`s to query the proberty`s from Thread`s.
I`m 100% agree to follow Jose`s sugestion!

First post i was not sure to use the Api Call "WaitForSingleObject()" together
with the PB Syntax...
But it works also nicely after "PB`s THREAD CLOSE".
I`ve simple Tested this by setting an Error: --> WaitForSingleObject(hThread(0)
Because it works general without any error.
'----====xxxxxxxxxxxxxxxxxxxxxxxxx-MciTHREAD-xxxxxxxxxxxxxxxxxxxxxxxx====----

FUNCTION PBMAIN () AS LONG

LOCAL s AS LONG
STATIC tx AS LONG
DIM hThread(1 TO 4) AS LOCAL DWORD

FOR tx = 1 TO 4
  THREAD CREATE Starter(tx) TO hThread(tx)

  THREAD STATUS hThread(tx) TO s
  IF s <> &H103 AND s <> hT THEN
     MSGBOX "Error!"
  ELSE
    ' MSGBOX  "Thread " + FORMAT$(tx) + " Started! " + $CRLF + _
    '         "Click -OK-  to finish!"
      SLEEP 4000
      THREAD CLOSE hThread(tx) TO s               `<-- PB Function

    ' WaitForSingleObject() provides an efficient method
    ' to wait for a thread to finish running.  In this
    ' instance, we'll wait for up to 10 seconds, which
    ' should be more than ample for this thread.

  IF WaitForSingleObject(hThread(tx), 10000) = _  ' <-- WinApi
     %WAIT_TIMEOUT THEN  ' There is a problem!
     MSGBOX "The thread has not responded to the shut-" _
      & "down signal within 10 seconds - Exit - Program...!!!"
       EXIT FUNCTION
   END IF
  END IF
NEXT
SLEEP 5000

END FUNCTION
'----====xxxxxxxxxxxxxxxxxxxxxxxxx-MciTHREAD-xxxxxxxxxxxxxxxxxxxxxxxx====----

Here`s just another resulting Code from the Test`s i did.
It shows how the Threads are running together at same time:

Good luck!
.¸.•'´¯)¸.•'´¯)¸.•'´¯)¸.•'´¯)
¤ª"˜¨¨¯¯¨¨˜"ª¤....¤ ª"˜¨

Petr Schreiber

Thank you for the example,

I learned something new!


Petr
AMD Sempron 3400+ | 1GB RAM @ 533MHz | GeForce 6200 / GeForce 9500GT | 32bit Windows XP SP3

psch.thinbasic.com

Jürgen Huhn

Peter,

i posted this also for other User`s...

Jürgen
.¸.•'´¯)¸.•'´¯)¸.•'´¯)¸.•'´¯)
¤ª"˜¨¨¯¯¨¨˜"ª¤....¤ ª"˜¨