2010-03-16 2 views
2

Je travaille sur une application qui lit l'audio en continu en utilisant l'API waveOut... de winmm.dll. L'application utilise des tampons "leapfrog", qui sont essentiellement un ensemble de tableaux d'échantillons que vous jetez dans la file d'attente audio. Windows les lit de manière homogène et chaque fois que la mémoire tampon est terminée, Windows appelle une fonction de rappel. À l'intérieur de cette fonction, je charge le jeu d'échantillons suivant dans le tampon, les traite cependant, puis restitue le tampon dans la file d'attente audio. De cette façon, l'audio joue indéfiniment.Problème avec le verrou waveOutWrite et waveOutGetPosition

Pour l'animation, j'essaie d'incorporer waveOutGetPosition dans l'application (puisque les rappels "buffer done" sont assez irréguliers pour provoquer une animation saccadée). waveOutGetPosition renvoie la position actuelle de la lecture, elle est donc hyper précise.

Le problème est que dans mon application, faire des appels à waveOutGetPosition finit par bloquer l'application - le son s'arrête et l'appel ne revient jamais. J'ai réduit les choses à une application simple qui démontre le problème. Vous pouvez exécuter l'application ici:

http://www.musigenesis.com/SO/waveOut%20demo.exe

Si vous entendez juste un petit peu de piano à plusieurs reprises, il travaille. C'est juste pour montrer le problème. Le code source de ce projet est ici (toute la viande est en LeapFrogPlayer.cs):

http://www.musigenesis.com/SO/WaveOutDemo.zip

Le premier bouton exécute l'application en mode saute-mouton sans faire les appels à waveOutGetPosition. Si vous cliquez dessus, l'application jouera pour toujours sans se casser (le bouton X le fermera et l'éteindra). Le second bouton démarre le saut de saut et démarre également un temporisateur de formulaires qui appelle le waveOutGetPosition et affiche la position actuelle. Cliquez ici et l'application fonctionnera pendant un court moment, puis se verrouiller. Sur mon ordinateur portable, il se verrouille habituellement en 15-30 secondes; tout au plus cela a pris une minute.

Je n'ai aucune idée de comment résoudre ce problème, donc toute aide ou suggestion serait la bienvenue. J'ai trouvé très peu de messages sur ce problème, mais il semble qu'il y ait un blocage potentiel, soit à partir de plusieurs appels à waveOutGetPosition ou à partir d'appels à cela et waveOutWrite qui se produisent en même temps. Il est possible que j'appelle ça trop souvent pour que le système puisse le gérer.

Modifier: oublié de mentionner, je cours sous Windows Vista. Cela peut ne pas arriver du tout sur les autres systèmes d'exploitation.

Edit 2: Je l'ai trouvé peu de choses sur ce problème en ligne, à l'exception de ces (sans réponses) messages:

http://social.msdn.microsoft.com/Forums/en-US/windowsgeneraldevelopmentissues/thread/c6a1e80e-4a18-47e7-af11-56a89f638ad7

Edit 3: Eh bien, je suis maintenant capable de reproduire ce problème à volonté. Si j'appelle waveOutGetPosition immédiatement après waveOutWrite (dans la ligne de code suivante) l'application se bloque à chaque fois. Il se bloque également de manière particulièrement mauvaise - il semble bloquer tout mon système d'exploitation pendant un certain temps, pas seulement l'application elle-même. Il semble donc que waveOutGetPosition deadlocks si elle se produit à presque en même temps que waveOutWrite, pas seulement littéralement en même temps, ce qui pourrait expliquer pourquoi les verrous ne fonctionnent pas pour moi. Yeesh.

Répondre

0

La solution à cela était très simple (merci à Larry Osterman): remplacer le rappel par un WndProc.

La méthode waveOutOpen peut prendre un délégué (pour le rappel) ou un handle de fenêtre. J'utilisais l'approche délégataire, qui est apparemment intrinsèquement encline à l'interblocage (logique, surtout dans le code managé). J'ai pu simplement hériter de ma classe de joueur de Control et remplacer la méthode WndProc, et faire la même chose dans cette méthode que je faisais dans le rappel. Maintenant, je peux appeler waveOutGetPosition pour toujours et il ne se bloque jamais.

3

Il se bloque dans le code API mmsys.Appeler waveOutGetPosition() dans les blocages de rappel lorsque le thread principal est occupé à exécuter waveOutWrite(). Il est réparable, vous aurez besoin d'un verrou pour que ces deux fonctions ne puissent pas s'exécuter en même temps. Ajouter ce champ à LeapFrogPlayer:

private object mLocker = new object(); 

et l'utiliser dans GetElapsedMilliseconds():

 if (!noAPIcall) 
     { 
      lock (mLocker) { 
      ret = WaveOutX.waveOutGetPosition(_hWaveOut, ref _timestruct, 
       _timestructsize); 
      } 
     } 

et HandleWaveCallback():

 // play the next buffer 
     lock (mLocker) { 
      int ret = WaveOutX.waveOutWrite(_hWaveOut, ref _header[_currentBuffer], 
       Marshal.SizeOf(_header[_currentBuffer])); 
      if (ret != WaveOutX.MMSYSERR_NOERROR) { 
      throw new Exception("error writing audio"); 
      } 
     } 

Cela pourrait avoir des effets secondaires, je n » Je ne remarque aucun. Jetez un oeil à la NAudio project.

Veuillez utiliser Build + Clean la prochaine fois que vous créerez un .zip téléchargeable de votre projet.

+0

J'ai essayé, mais il est toujours en train de se bloquer. Je vais essayer de verrouiller les appels à waveOutPrepareHeader, aussi. Pourquoi avez-vous besoin de Build + Clean? Était-ce difficile de compiler le projet ou quelque chose? – MusiGenesis

+2

A travaillé sur ma machine. Utilisez Debug + Break All, Debug + Windows + Threads pour voir où les threads sont bloqués. Personne n'aime les gros téléchargements avec des fichiers .exe provenant d'une URL Internet non fiable. –

+0

Si quoi que ce soit, cette modification aggrave le problème. Cela semble se bloquer beaucoup plus vite, maintenant; environ la moitié du temps maintenant, il se bloque au premier appel à waveOutGetPosition. Est-ce que cela fonctionne sur votre ordinateur? – MusiGenesis

1

J'utilise NAudio et interroger WaveOut.GetPosition() fréquemment, et également voir des blocages fréquents lors de l'utilisation de la stratégie Callback. C'est essentiellement le même problème que le PO avait, donc je pense que cette solution pourrait aider quelqu'un d'autre.

J'ai essayé d'utiliser des stratégies basées sur des fenêtres (comme indiqué dans la réponse) mais l'audio bégayerait lorsque de nombreux messages étaient envoyés dans la file d'attente de messages. Je suis donc passé à la stratégie Callback. Puis j'ai commencé à avoir des blocages. Je demande une position audio à 60 ips pour synchroniser une animation, donc je suis en train de frapper l'impasse assez régulièrement (environ 20 secondes dans une course en moyenne). Note:Je suis sûr que je peux réduire le montant que j'appelle l'API, mais ce n'est pas mon point ici!

Il semble que les appels winmm.dll se verrouillent tous en interne sur le même objet/handle. Si cette supposition est vraie, alors je suis presque assuré d'une impasse dans NAudio. Voici le scénario avec deux threads: A (thread UI); et B (thread de rappel dans winmm.dll) et deux verrous waveOutLock (comme dans NAudio) et mmdll (le verrou que je suppose winmm.dll utilise):

  1. A -> serrure (waveOutLock) --- acquis
  2. B -> serrure (mmdll) pour le rappel --- acquis
  3. B -> rappel dans le code d'utilisateur
  4. B -> tentative de verrouillage (waveOutLock) - attente A pour libérer
  5. A -> B repris en raison de l'attente
  6. A -> appelez waveOutGetPosition
  7. A -> tentative de verrouillage (mmdll) - impasse

Ma solution a été de déléguer le travail accompli dans le rappel à mon propre fil de sorte que le rappel peut revenir immédiatement et relâchez le (hypothétique) verrouillage mmdll. Cela semble fonctionner parfaitement pour moi, car l'impasse est partie.

Pour ceux qui sont intéressés, j'ai forked and modified la source NAudio pour inclure mon changement. J'ai utilisé le pool de threads, et l'audio est parfois un peu craquante. Cela peut être dû à la gestion des threads du pool de threads, il peut donc y avoir une solution qui fonctionne mieux.

+0

Vous pouvez tester cette solution très fortement et vous assurer qu'elle fonctionne à 100%.Dans ma vraie application, le travail qui nécessitait l'appel 'waveOutGetPosition' a été fait sur un thread séparé. Cela fonctionnait plutôt bien, mais il arrivait parfois que l'impasse se produise - beaucoup moins fréquemment que dans l'application de démonstration que je publiais ici, mais assez souvent pour m'inquiéter encore beaucoup. Dans ma version actuelle en utilisant l'approche wndProc, le moteur de lecture est fiable à 100%; Je l'ai laissé courir pendant des jours à la fois et je n'ai pas vu un seul exemple de l'impasse. Mais en utilisant le rappel, il fonctionnerait rarement ... – MusiGenesis

+0

... pendant plus d'une heure ou deux en continu avant le verrouillage, et il se verrouillerait de temps en temps après quelques secondes ou minutes. Donc, assurez-vous de faire des tests très longs, donc vous savez que vous l'avez. – MusiGenesis

+0

BTW, je n'ai jamais entendu aucune sorte de bégaiement comme ce que vous décrivez en utilisant l'approche wndProc. Que se passe-t-il dans votre programme et votre système lorsque cela se produit? – MusiGenesis

Questions connexes