Index: trunk/src/VBox/Devices/Audio/AudioMixer.cpp
===================================================================
--- trunk/src/VBox/Devices/Audio/AudioMixer.cpp	(revision 88987)
+++ trunk/src/VBox/Devices/Audio/AudioMixer.cpp	(revision 88991)
@@ -105,5 +105,5 @@
 static int audioMixerSinkInit(PAUDMIXSINK pSink, PAUDIOMIXER pMixer, const char *pcszName, PDMAUDIODIR enmDir, PPDMDEVINS pDevIns);
 static void audioMixerSinkDestroyInternal(PAUDMIXSINK pSink, PPDMDEVINS pDevIns);
-static int audioMixerSinkUpdateVolume(PAUDMIXSINK pSink, const PPDMAUDIOVOLUME pVolMaster);
+static int audioMixerSinkUpdateVolume(PAUDMIXSINK pSink, PCPDMAUDIOVOLUME pVolMaster);
 static void audioMixerSinkRemoveAllStreamsInternal(PAUDMIXSINK pSink);
 static int audioMixerSinkRemoveStreamInternal(PAUDMIXSINK pSink, PAUDMIXSTREAM pStream);
@@ -117,5 +117,5 @@
 
 /** size of output buffer for dbgAudioMixerSinkStatusToStr.   */
-#define AUDIOMIXERSINK_STATUS_STR_MAX sizeof("RUNNING PENDING_DISABLE DIRTY 0x12345678")
+#define AUDIOMIXERSINK_STATUS_STR_MAX sizeof("RUNNING DRAINING DRAINED_DMA DRAINED_MIXBUF DIRTY 0x12345678")
 
 /**
@@ -138,7 +138,9 @@
     } s_aFlags[] =
     {
-        { RT_STR_TUPLE("RUNNING "),          AUDMIXSINK_STS_RUNNING },
-        { RT_STR_TUPLE("PENDING_DISABLE "),  AUDMIXSINK_STS_PENDING_DISABLE },
-        { RT_STR_TUPLE("DIRTY "),            AUDMIXSINK_STS_DIRTY },
+        { RT_STR_TUPLE("RUNNING "),         AUDMIXSINK_STS_RUNNING },
+        { RT_STR_TUPLE("DRAINING "),        AUDMIXSINK_STS_DRAINING },
+        { RT_STR_TUPLE("DRAINED_DMA"),      AUDMIXSINK_STS_DRAINED_DMA },
+        { RT_STR_TUPLE("DRAINED_MIXBUF"),   AUDMIXSINK_STS_DRAINED_MIXBUF },
+        { RT_STR_TUPLE("DIRTY "),           AUDMIXSINK_STS_DIRTY },
     };
     char *psz = pszDst;
@@ -248,4 +250,5 @@
 
             pMixer->fFlags = fFlags;
+            pMixer->uMagic = AUDIOMIXER_MAGIC;
 
             if (pMixer->fFlags & AUDMIXER_FLAGS_DEBUG)
@@ -282,14 +285,14 @@
 {
     RT_NOREF(pszArgs);
+    Assert(pMixer->uMagic == AUDIOMIXER_MAGIC);
+
+    int rc2 = RTCritSectEnter(&pMixer->CritSect);
+    AssertRCReturnVoid(rc2);
+
+    pHlp->pfnPrintf(pHlp, "[Master] %s: lVol=%u, rVol=%u, fMuted=%RTbool\n", pMixer->pszName,
+                    pMixer->VolMaster.uLeft, pMixer->VolMaster.uRight, pMixer->VolMaster.fMuted);
+
     PAUDMIXSINK pSink;
     unsigned    iSink = 0;
-
-    int rc2 = RTCritSectEnter(&pMixer->CritSect);
-    if (RT_FAILURE(rc2))
-        return;
-
-    pHlp->pfnPrintf(pHlp, "[Master] %s: lVol=%u, rVol=%u, fMuted=%RTbool\n", pMixer->pszName,
-                    pMixer->VolMaster.uLeft, pMixer->VolMaster.uRight, pMixer->VolMaster.fMuted);
-
     RTListForEach(&pMixer->lstSinks, pSink, AUDMIXSINK, Node)
     {
@@ -313,4 +316,6 @@
     if (!pMixer)
         return;
+    AssertPtr(pMixer);
+    Assert(pMixer->uMagic == AUDIOMIXER_MAGIC);
 
     int rc2 = RTCritSectEnter(&pMixer->CritSect);
@@ -318,4 +323,5 @@
 
     LogFlowFunc(("Destroying %s ...\n", pMixer->pszName));
+    pMixer->uMagic = AUDIOMIXER_MAGIC_DEAD;
 
     PAUDMIXSINK pSink, pSinkNext;
@@ -347,8 +353,7 @@
  * @param   pMixer              Mixer to invalidate data for.
  */
-int audioMixerInvalidateInternal(PAUDIOMIXER pMixer)
+static int audioMixerInvalidateInternal(PAUDIOMIXER pMixer)
 {
     AssertPtrReturn(pMixer, VERR_INVALID_POINTER);
-
     LogFlowFunc(("[%s]\n", pMixer->pszName));
 
@@ -374,4 +379,5 @@
 {
     AssertPtrReturnVoid(pMixer);
+    Assert(pMixer->uMagic == AUDIOMIXER_MAGIC);
 
     int rc2 = RTCritSectEnter(&pMixer->CritSect);
@@ -453,9 +459,9 @@
 {
     AssertPtrReturn(pMixer, VERR_INVALID_POINTER);
+    Assert(pMixer->uMagic == AUDIOMIXER_MAGIC);
     AssertPtrReturn(pVol,   VERR_INVALID_POINTER);
 
     int rc = RTCritSectEnter(&pMixer->CritSect);
-    if (RT_FAILURE(rc))
-        return rc;
+    AssertRCReturn(rc, rc);
 
     memcpy(&pMixer->VolMaster, pVol, sizeof(PDMAUDIOVOLUME));
@@ -478,4 +484,21 @@
 *********************************************************************************************************************************/
 
+#ifdef VBOX_STRICT
+/**
+ * Checks if @a pNeedle is in the list of streams associated with @a pSink.
+ * @returns true / false.
+ */
+static bool audioMixerSinkIsStreamInList(PAUDMIXSINK pSink, PAUDMIXSTREAM pNeedle)
+{
+    PAUDMIXSTREAM pStream;
+    RTListForEach(&pSink->lstStreams, pStream, AUDMIXSTREAM, Node)
+    {
+        if (pStream == pNeedle)
+            return true;
+    }
+    return false;
+}
+#endif /* VBOX_STRICT */
+
 /**
  * Adds an audio stream to a specific audio sink.
@@ -489,5 +512,7 @@
     LogFlowFuncEnter();
     AssertPtrReturn(pSink,   VERR_INVALID_POINTER);
+    Assert(pSink->uMagic == AUDMIXSINK_MAGIC);
     AssertPtrReturn(pStream, VERR_INVALID_POINTER);
+    Assert(pStream->uMagic == AUDMIXSTREAM_MAGIC);
     AssertPtrReturn(pStream->pConn, VERR_AUDIO_STREAM_NOT_READY);
     AssertReturn(pStream->pSink == NULL, VERR_ALREADY_EXISTS);
@@ -504,5 +529,5 @@
      */
     if (    (pSink->fStatus & AUDMIXSINK_STS_RUNNING)
-        && !(pSink->fStatus & AUDMIXSINK_STS_PENDING_DISABLE))
+        && !(pSink->fStatus & AUDMIXSINK_STS_DRAINING))
     {
         audioMixerStreamCtlInternal(pStream, PDMAUDIOSTREAMCMD_ENABLE);
@@ -544,8 +569,9 @@
 {
     AssertPtrReturn(pSink, VERR_INVALID_POINTER);
+    Assert(pSink->uMagic == AUDMIXSINK_MAGIC);
     AssertPtrReturn(pConn, VERR_INVALID_POINTER);
     AssertPtrReturn(pCfg, VERR_INVALID_POINTER);
     AssertPtrNullReturn(ppStream, VERR_INVALID_POINTER);
-    RT_NOREF(pDevIns); /* we'll probably be adding more statistics */
+    Assert(pSink->AIO.pDevIns == pDevIns); RT_NOREF(pDevIns); /* we'll probably be adding more statistics */
 
     /*
@@ -623,4 +649,6 @@
                 if (RT_SUCCESS(rc))
                 {
+                    pMixStream->cFramesBackendBuffer = CfgHost.Backend.cFramesBufferSize;
+
                     /* Set up the mixing buffer conversion state. */
                     if (pSink->enmDir == PDMAUDIODIR_OUT)
@@ -632,7 +660,8 @@
 
                         /* Increase the stream's reference count to let others know
-                         * we're reyling on it to be around now. */
+                         * we're relying on it to be around now. */
                         pConn->pfnStreamRetain(pConn, pStream);
-                        pMixStream->pConn = pConn;
+                        pMixStream->pConn  = pConn;
+                        pMixStream->uMagic = AUDMIXSTREAM_MAGIC;
 
                         RTCritSectLeave(&pSink->CritSect);
@@ -666,91 +695,214 @@
 }
 
-/**
- * Enables or disables a mixer sink.
- *
- * @returns VBox status code.
- *          Generally always VINF_SUCCESS unless the input is invalid.
- *          Individual driver errors are suppressed and ignored.
+
+/**
+ * Starts playback/capturing on the mixer sink.
+ *
+ * @returns VBox status code.  Generally always VINF_SUCCESS unless the input
+ *          is invalid.  Individual driver errors are suppressed and ignored.
  * @param   pSink       Mixer sink to control.
- * @param   fEnable     Whether to enable or disable the sink.
- */
-int AudioMixerSinkEnable(PAUDMIXSINK pSink, bool fEnable)
+ */
+int AudioMixerSinkStart(PAUDMIXSINK pSink)
 {
     AssertPtrReturn(pSink, VERR_INVALID_POINTER);
-    PDMAUDIOSTREAMCMD const enmCmd = fEnable ? PDMAUDIOSTREAMCMD_ENABLE : PDMAUDIOSTREAMCMD_DISABLE;
-
+    Assert(pSink->uMagic == AUDMIXSINK_MAGIC);
     int rc = RTCritSectEnter(&pSink->CritSect);
     AssertRCReturn(rc, rc);
+    char szStatus[AUDIOMIXERSINK_STATUS_STR_MAX];
+    LogFunc(("Starting '%s'. Old status: %s\n", pSink->pszName, dbgAudioMixerSinkStatusToStr(pSink->fStatus, szStatus)));
 
     /*
-     * Input sink and no recording source set? Return early without updating the stream status...
+     * Make sure the sink and its streams are all stopped.
      */
-    if (   pSink->enmDir == PDMAUDIODIR_IN
-        && pSink->In.pStreamRecSource == NULL)
-    {
-        /** @todo r=bird: will the cause trouble for the stream status management? */
-    }
+    if (!(pSink->fStatus & AUDMIXSINK_STS_RUNNING))
+        Assert(pSink->fStatus == AUDMIXSINK_STS_NONE);
     else
     {
+        LogFunc(("%s: This sink is still running!! Stop it before starting it again.\n", pSink->pszName));
+
+        PAUDMIXSTREAM pStream;
+        RTListForEach(&pSink->lstStreams, pStream, AUDMIXSTREAM, Node)
+        {
+            /** @todo PDMAUDIOSTREAMCMD_STOP_NOW   */
+            audioMixerStreamCtlInternal(pStream, PDMAUDIOSTREAMCMD_DISABLE);
+        }
+        audioMixerSinkReset(pSink);
+    }
+
+    /*
+     * Send the command to the streams.
+     */
+    if (pSink->enmDir == PDMAUDIODIR_OUT)
+    {
+        PAUDMIXSTREAM pStream;
+        RTListForEach(&pSink->lstStreams, pStream, AUDMIXSTREAM, Node)
+        {
+            audioMixerStreamCtlInternal(pStream, PDMAUDIOSTREAMCMD_ENABLE);
+        }
+    }
+    else
+    {
+        AssertReturnStmt(pSink->enmDir == PDMAUDIODIR_IN, RTCritSectLeave(&pSink->CritSect), VERR_INTERNAL_ERROR_3);
+        if (pSink->In.pStreamRecSource) /* Any recording source set? */
+        {
+            Assert(audioMixerSinkIsStreamInList(pSink, pSink->In.pStreamRecSource));
+            audioMixerStreamCtlInternal(pSink->In.pStreamRecSource, PDMAUDIOSTREAMCMD_ENABLE);
+        }
+    }
+
+    /*
+     * Update the sink status.
+     */
+    pSink->fStatus = AUDMIXSINK_STS_RUNNING;
+
+    LogRel2(("Audio Mixer: Started sink '%s': %s\n", pSink->pszName, dbgAudioMixerSinkStatusToStr(pSink->fStatus, szStatus)));
+
+    RTCritSectLeave(&pSink->CritSect);
+    return VINF_SUCCESS;
+}
+
+
+/**
+ * Helper for AudioMixerSinkDrainAndStop that calculates the max length a drain
+ * operation should take.
+ *
+ * @returns The drain deadline (relative to RTTimeNanoTS).
+ * @param   pSink               The sink.
+ * @param   cbDmaLeftToDrain    The number of bytes in the DMA buffer left to
+ *                              transfer into the mixbuf.
+ */
+static uint64_t audioMixerSinkDrainDeadline(PAUDMIXSINK pSink, uint32_t cbDmaLeftToDrain)
+{
+    /*
+     * Calculate the max backend buffer size in mixbuf frames.
+     * (This is somewhat similar to audioMixerSinkUpdateOutputCalcFramesToRead.)
+     */
+    uint32_t      cFramesStreamMax = 0;
+    PAUDMIXSTREAM pMixStream;
+    RTListForEach(&pSink->lstStreams, pMixStream, AUDMIXSTREAM, Node)
+    {
+        /*LogFunc(("Stream '%s': %#x (%u frames)\n", pMixStream->pszName, pMixStream->fStatus, pMixStream->cFramesBackendBuffer));*/
+        if (pMixStream->fStatus & AUDMIXSTREAM_STATUS_CAN_WRITE)
+        {
+            uint32_t cFrames = pMixStream->cFramesBackendBuffer;
+            if (PDMAudioPropsHz(&pMixStream->pStream->Props) == PDMAudioPropsHz(&pSink->MixBuf.Props))
+            { /* likely */ }
+            else
+                cFrames = cFrames * PDMAudioPropsHz(&pSink->MixBuf.Props) / PDMAudioPropsHz(&pMixStream->pStream->Props);
+            if (cFrames > cFramesStreamMax)
+            {
+                Log4Func(("%s: cFramesStreamMax %u -> %u; %s\n", pSink->pszName, cFramesStreamMax, cFrames, pMixStream->pszName));
+                cFramesStreamMax = cFrames;
+            }
+        }
+    }
+
+    /*
+     * Combine that with the pending DMA and mixbuf content, then convert
+     * to nanoseconds and apply a fudge factor to get a generous deadline.
+     */
+    uint32_t const cFramesDmaAndMixBuf = PDMAudioPropsBytesToFrames(&pSink->MixBuf.Props, cbDmaLeftToDrain)
+                                       + AudioMixBufLive(&pSink->MixBuf);
+    uint64_t const cNsToDrainMax       = PDMAudioPropsFramesToNano(&pSink->MixBuf.Props, cFramesDmaAndMixBuf + cFramesStreamMax);
+    uint64_t const nsDeadline          = cNsToDrainMax * 2;
+    LogFlowFunc(("%s: cFramesStreamMax=%#x cFramesDmaAndMixBuf=%#x -> cNsToDrainMax=%RU64 -> %RU64\n",
+                 pSink->pszName, cFramesStreamMax, cFramesDmaAndMixBuf, cNsToDrainMax, nsDeadline));
+    return nsDeadline;
+}
+
+
+/**
+ * Kicks off the draining and stopping playback/capture on the mixer sink.
+ *
+ * For input streams this causes an immediate stop, as draining only makes sense
+ * to output stream in the VBox device context.
+ *
+ * @returns VBox status code.  Generally always VINF_SUCCESS unless the input
+ *          is invalid.  Individual driver errors are suppressed and ignored.
+ * @param   pSink       Mixer sink to control.
+ * @param   cbComming   The number of bytes still left in the device's DMA
+ *                      buffers that the update job has yet to transfer.  This
+ *                      is ignored for input streams.
+ */
+int AudioMixerSinkDrainAndStop(PAUDMIXSINK pSink, uint32_t cbComming)
+{
+    AssertPtrReturn(pSink, VERR_INVALID_POINTER);
+    Assert(pSink->uMagic == AUDMIXSINK_MAGIC);
+
+    int rc = RTCritSectEnter(&pSink->CritSect);
+    AssertRCReturn(rc, rc);
+    char szStatus[AUDIOMIXERSINK_STATUS_STR_MAX];
+    LogFunc(("Draining '%s' with %#x bytes left. Old status: %s\n",
+             pSink->pszName, cbComming, dbgAudioMixerSinkStatusToStr(pSink->fStatus, szStatus) ));
+
+    if (pSink->fStatus & AUDMIXSINK_STS_RUNNING)
+    {
         /*
-         * Send the command to the streams.
+         * Output streams will be drained then stopped (all by the AIO thread).
+         *
+         * For streams we define that they shouldn't not be written to after we start draining,
+         * so we have to hold back sending the command to them till we've processed all the
+         * cbComming remaining bytes in the DMA buffer.
          */
-        if (pSink->enmDir == PDMAUDIODIR_IN)
-        {
-            /** @todo r=bird: we already check this. duh.   */
+        if (pSink->enmDir == PDMAUDIODIR_OUT)
+        {
+            if (!(pSink->fStatus & AUDMIXSINK_STS_DRAINING))
+            {
+                Assert(!(pSink->fStatus & (AUDMIXSINK_STS_DRAINED_DMA | AUDMIXSINK_STS_DRAINED_MIXBUF)));
+
+                /* Update the status and draining member. */
+                pSink->cbDmaLeftToDrain = cbComming;
+                pSink->nsDrainDeadline  = audioMixerSinkDrainDeadline(pSink, cbComming);
+                if (pSink->nsDrainDeadline > 0)
+                {
+                    pSink->nsDrainStarted   = RTTimeNanoTS();
+                    pSink->nsDrainDeadline += pSink->nsDrainStarted;
+                    pSink->fStatus         |= AUDMIXSINK_STS_DRAINING;
+
+                    /* Kick the AIO thread so it can keep pushing data till we're out of this
+                       status. (The device's DMA timer won't kick it any more, so we must.) */
+                    AudioMixerSinkSignalUpdateJob(pSink);
+                }
+                else
+                {
+                    LogFunc(("%s: No active streams, doing an immediate stop.\n"));
+                    PAUDMIXSTREAM pStream;
+                    RTListForEach(&pSink->lstStreams, pStream, AUDMIXSTREAM, Node)
+                    {
+                        audioMixerStreamCtlInternal(pStream, PDMAUDIOSTREAMCMD_DISABLE);
+                    }
+                    audioMixerSinkReset(pSink);
+                }
+            }
+            else
+                AssertMsgFailed(("Already draining '%s': %s\n",
+                                 pSink->pszName, dbgAudioMixerSinkStatusToStr(pSink->fStatus, szStatus)));
+        }
+        /*
+         * Input sinks are stopped immediately.
+         *
+         * It's the guest giving order here and we can't force it to accept data that's
+         * already in the buffer pipeline or anything.  So, there can be no draining here.
+         */
+        else
+        {
+            AssertReturnStmt(pSink->enmDir == PDMAUDIODIR_IN, RTCritSectLeave(&pSink->CritSect), VERR_INTERNAL_ERROR_3);
             if (pSink->In.pStreamRecSource) /* Any recording source set? */
             {
-                PAUDMIXSTREAM pStream;
-                RTListForEach(&pSink->lstStreams, pStream, AUDMIXSTREAM, Node)
-                {
-                    if (pStream == pSink->In.pStreamRecSource)
-                    {
-                        audioMixerStreamCtlInternal(pStream, enmCmd);
-                        break;
-                    }
-                }
+                Assert(audioMixerSinkIsStreamInList(pSink, pSink->In.pStreamRecSource));
+                audioMixerStreamCtlInternal(pSink->In.pStreamRecSource, PDMAUDIOSTREAMCMD_DISABLE);
             }
-        }
-        else if (pSink->enmDir == PDMAUDIODIR_OUT)
-        {
-            PAUDMIXSTREAM pStream;
-            RTListForEach(&pSink->lstStreams, pStream, AUDMIXSTREAM, Node)
-            {
-                audioMixerStreamCtlInternal(pStream, enmCmd);
-            }
-        }
-        else
-            AssertFailed();
-
-        /*
-         * Update the sink status.
-         */
-        if (fEnable)
-        {
-            /* Make sure to clear any other former flags again by assigning AUDMIXSINK_STS_RUNNING directly. */
-            pSink->fStatus = AUDMIXSINK_STS_RUNNING;
-        }
-        else
-        {
-            if (pSink->fStatus & AUDMIXSINK_STS_RUNNING)
-            {
-                /* Set the sink in a pending disable state first.
-                 * The final status (disabled) will be set in the sink's iteration. */
-                pSink->fStatus |= AUDMIXSINK_STS_PENDING_DISABLE;
-
-                /* Kick the AIO thread so it can keep pushing data till we're out of this status. */
-                if (pSink->AIO.hEvent != NIL_RTSEMEVENT)
-                    AudioMixerSinkSignalUpdateJob(pSink);
-            }
-        }
-    }
-
-    char szStatus[AUDIOMIXERSINK_STATUS_STR_MAX];
-    LogRel2(("Audio Mixer: Set new status of sink '%s': %s (enmCmd=%s)\n",
-             pSink->pszName, dbgAudioMixerSinkStatusToStr(pSink->fStatus, szStatus), PDMAudioStrmCmdGetName(enmCmd)));
-
+            audioMixerSinkReset(pSink);
+        }
+    }
+    else
+        LogFunc(("%s: Not running\n", pSink->pszName));
+
+    LogRel2(("Audio Mixer: Started draining sink '%s': %s\n", pSink->pszName, dbgAudioMixerSinkStatusToStr(pSink->fStatus, szStatus)));
     RTCritSectLeave(&pSink->CritSect);
     return VINF_SUCCESS;
 }
+
+
 
 /**
@@ -773,4 +925,5 @@
     if (RT_SUCCESS(rc))
     {
+        pSink->uMagic   = AUDMIXSINK_MAGIC;
         pSink->pParent  = pMixer;
         pSink->enmDir   = enmDir;
@@ -823,4 +976,5 @@
         PAUDIOMIXER pMixer = pSink->pParent;
         AssertPtr(pMixer);
+        Assert(pMixer->uMagic == AUDIOMIXER_MAGIC);
 
         audioMixerRemoveSinkInternal(pMixer, pSink);
@@ -848,9 +1002,12 @@
     LogFunc(("%s\n", pSink->pszName));
 
+    Assert(pSink->uMagic == AUDMIXSINK_MAGIC);
+    pSink->uMagic = AUDMIXSINK_MAGIC_DEAD;
+
     PAUDMIXSTREAM pStream, pStreamNext;
     RTListForEachSafe(&pSink->lstStreams, pStream, pStreamNext, AUDMIXSTREAM, Node)
     {
         audioMixerSinkRemoveStreamInternal(pSink, pStream);
-        audioMixerStreamDestroyInternal(pStream, pDevIns);
+        audioMixerStreamDestroyInternal(pStream, pDevIns); /* (Unlike the other two, this frees the stream structure.) */
     }
 
@@ -904,10 +1061,9 @@
 {
     AssertPtrReturn(pSink, 0);
-
+    Assert(pSink->uMagic == AUDMIXSINK_MAGIC);
     AssertMsg(pSink->enmDir == PDMAUDIODIR_IN, ("%s: Can't read from a non-input sink\n", pSink->pszName));
 
     int rc = RTCritSectEnter(&pSink->CritSect);
-    if (RT_FAILURE(rc))
-        return 0;
+    AssertRCReturn(rc, 0);
 
     uint32_t cbReadable = 0;
@@ -947,7 +1103,7 @@
 PAUDMIXSTREAM AudioMixerSinkGetRecordingSource(PAUDMIXSINK pSink)
 {
+    Assert(pSink->uMagic == AUDMIXSINK_MAGIC);
     int rc = RTCritSectEnter(&pSink->CritSect);
-    if (RT_FAILURE(rc))
-        return NULL;
+    AssertRCReturn(rc, NULL);
 
     AssertMsg(pSink->enmDir == PDMAUDIODIR_IN, ("Specified sink is not an input sink\n"));
@@ -971,15 +1127,14 @@
 {
     AssertPtrReturn(pSink, 0);
-
+    Assert(pSink->uMagic == AUDMIXSINK_MAGIC);
     AssertMsg(pSink->enmDir == PDMAUDIODIR_OUT, ("%s: Can't write to a non-output sink\n", pSink->pszName));
 
     int rc = RTCritSectEnter(&pSink->CritSect);
-    if (RT_FAILURE(rc))
-        return 0;
+    AssertRCReturn(rc, 0);
 
     uint32_t cbWritable = 0;
 
     if (    (pSink->fStatus & AUDMIXSINK_STS_RUNNING)
-        && !(pSink->fStatus & AUDMIXSINK_STS_PENDING_DISABLE))
+        && !(pSink->fStatus & AUDMIXSINK_STS_DRAINING))
     {
         cbWritable = AudioMixBufFreeBytes(&pSink->MixBuf);
@@ -1004,4 +1159,5 @@
 {
     AssertPtrReturn(pSink, PDMAUDIODIR_INVALID);
+    Assert(pSink->uMagic == AUDMIXSINK_MAGIC);
 
     /** @todo the sink direction should be static...    */
@@ -1027,8 +1183,9 @@
     if (!pSink)
         return AUDMIXSINK_STS_NONE;
+    AssertPtr(pSink);
+    Assert(pSink->uMagic == AUDMIXSINK_MAGIC);
 
     int rc2 = RTCritSectEnter(&pSink->CritSect);
-    if (RT_FAILURE(rc2))
-        return AUDMIXSINK_STS_NONE;
+    AssertRCReturn(rc2, AUDMIXSINK_STS_NONE);
 
     /* If the dirty flag is set, there is unprocessed data in the sink. */
@@ -1053,8 +1210,9 @@
     if (!pSink)
         return false;
+    AssertPtr(pSink);
+    Assert(pSink->uMagic == AUDMIXSINK_MAGIC);
 
     int rc2 = RTCritSectEnter(&pSink->CritSect);
-    if (RT_FAILURE(rc2))
-        return false;
+    AssertRCReturn(rc2, false);
 
     const bool fIsActive = pSink->fStatus & AUDMIXSINK_STS_RUNNING;
@@ -1081,4 +1239,5 @@
 {
     AssertPtrReturn(pSink, VERR_INVALID_POINTER);
+    Assert(pSink->uMagic == AUDMIXSINK_MAGIC);
     AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
     AssertReturn(cbBuf,    VERR_INVALID_PARAMETER);
@@ -1216,4 +1375,9 @@
 void AudioMixerSinkRemoveStream(PAUDMIXSINK pSink, PAUDMIXSTREAM pStream)
 {
+    AssertPtr(pSink);
+    Assert(pSink->uMagic == AUDMIXSINK_MAGIC);
+    if (pStream)
+        Assert(pStream->uMagic == AUDMIXSTREAM_MAGIC);
+
     int rc2 = RTCritSectEnter(&pSink->CritSect);
     AssertRC(rc2);
@@ -1248,13 +1412,34 @@
 
 /**
- * Resets the sink's state.
- *
- * @param   pSink               Sink to reset.
- */
-static void audioMixerSinkReset(PAUDMIXSINK pSink)
+ * Removes all attached streams from a given sink.
+ *
+ * @param pSink                 Sink to remove attached streams from.
+ */
+void AudioMixerSinkRemoveAllStreams(PAUDMIXSINK pSink)
 {
     if (!pSink)
         return;
 
+    int rc2 = RTCritSectEnter(&pSink->CritSect);
+    AssertRC(rc2);
+
+    audioMixerSinkRemoveAllStreamsInternal(pSink);
+
+    pSink->cStreams = 0;
+
+    rc2 = RTCritSectLeave(&pSink->CritSect);
+    AssertRC(rc2);
+}
+
+/**
+ * Resets the sink's state.
+ *
+ * @param   pSink               Sink to reset.
+ */
+static void audioMixerSinkReset(PAUDMIXSINK pSink)
+{
+    if (!pSink)
+        return;
+
     LogFunc(("[%s]\n", pSink->pszName));
 
@@ -1266,25 +1451,4 @@
     /* Reset status. */
     pSink->fStatus = AUDMIXSINK_STS_NONE;
-}
-
-/**
- * Removes all attached streams from a given sink.
- *
- * @param pSink                 Sink to remove attached streams from.
- */
-void AudioMixerSinkRemoveAllStreams(PAUDMIXSINK pSink)
-{
-    if (!pSink)
-        return;
-
-    int rc2 = RTCritSectEnter(&pSink->CritSect);
-    AssertRC(rc2);
-
-    audioMixerSinkRemoveAllStreamsInternal(pSink);
-
-    pSink->cStreams = 0;
-
-    rc2 = RTCritSectLeave(&pSink->CritSect);
-    AssertRC(rc2);
 }
 
@@ -1320,4 +1484,5 @@
 {
     AssertPtrReturn(pSink,     VERR_INVALID_POINTER);
+    Assert(pSink->uMagic == AUDMIXSINK_MAGIC);
     AssertPtrReturn(pPCMProps, VERR_INVALID_POINTER);
     AssertReturn(AudioHlpPcmPropsAreValid(pPCMProps), VERR_INVALID_PARAMETER);
@@ -1467,8 +1632,10 @@
 {
     AssertPtrReturn(pSink, VERR_INVALID_POINTER);
+    Assert(pSink->uMagic == AUDMIXSINK_MAGIC);
+    if (pStream)
+        Assert(pStream->uMagic == AUDMIXSTREAM_MAGIC);
 
     int rc = RTCritSectEnter(&pSink->CritSect);
-    if (RT_FAILURE(rc))
-        return rc;
+    AssertRCReturn(rc, rc);
 
     rc = audioMixerSinkSetRecSourceInternal(pSink, pStream);
@@ -1490,9 +1657,9 @@
 {
     AssertPtrReturn(pSink, VERR_INVALID_POINTER);
+    Assert(pSink->uMagic == AUDMIXSINK_MAGIC);
     AssertPtrReturn(pVol,  VERR_INVALID_POINTER);
 
     int rc = RTCritSectEnter(&pSink->CritSect);
-    if (RT_FAILURE(rc))
-        return rc;
+    AssertRCReturn(rc, rc);
 
     memcpy(&pSink->Volume, pVol, sizeof(PDMAUDIOVOLUME));
@@ -1579,34 +1746,5 @@
     pSink->tsLastUpdatedMs = RTTimeMilliTS();
 
-    /*
-     * Deal with pending disable.  The general case is that we reset
-     * the sink when all streams have been disabled, however input is
-     * currently a special case where we only care about the one
-     * recording source...
-     */
-    if (pSink->fStatus & AUDMIXSINK_STS_PENDING_DISABLE)
-    {
-#if 1
-        uint32_t const  cStreams         = 1;
-        uint32_t        cStreamsDisabled = 1;
-        pMixStream = pSink->In.pStreamRecSource;
-#else
-        uint32_t const  cStreams         = pSink->cStreams;
-        uint32_t        cStreamsDisabled = pSink->cStreams;
-        RTListForEach(&pSink->lstStreams, pMixStream, AUDMIXSTREAM, Node)
-#endif
-        {
-            if (pMixStream->fStatus & AUDMIXSTREAM_STATUS_ENABLED)
-            {
-                PDMAUDIOSTREAMSTATE const enmState = pMixStream->pConn->pfnStreamGetState(pMixStream->pConn, pMixStream->pStream);
-                if (enmState >= PDMAUDIOSTREAMSTATE_ENABLED)
-                    cStreamsDisabled--;
-            }
-        }
-        Log3Func(("[%s] pending disable: %u of %u disabled\n", pSink->pszName, cStreamsDisabled, cStreams));
-        if (cStreamsDisabled == cStreams)
-            audioMixerSinkReset(pSink);
-    }
-
+    Assert(!(pSink->fStatus & AUDMIXSINK_STS_DRAINING));
     return rc;
 }
@@ -1674,4 +1812,5 @@
 {
     PAUDMIXSTREAM pMixStream;
+    Assert(!(pSink->fStatus & AUDMIXSINK_STS_DRAINED_MIXBUF) || AudioMixBufUsed(&pSink->MixBuf) == 0);
 
     /*
@@ -1814,5 +1953,5 @@
          * Update the dirty flag for what it's worth.
          */
-        if (AudioMixBufUsed(&pSink->MixBuf))
+        if (AudioMixBufUsed(&pSink->MixBuf) > 0)
             pSink->fStatus |= AUDMIXSINK_STS_DIRTY;
         else
@@ -1838,5 +1977,6 @@
 
     /* Update last updated timestamp. */
-    pSink->tsLastUpdatedMs = RTTimeMilliTS();
+    uint64_t const nsNow   = RTTimeNanoTS();
+    pSink->tsLastUpdatedMs = nsNow / RT_NS_1MS;
 
     /*
@@ -1844,6 +1984,25 @@
      * We reset the sink when all streams have been disabled.
      */
-    if (pSink->fStatus & AUDMIXSINK_STS_PENDING_DISABLE)
-    {
+    if (!(pSink->fStatus & AUDMIXSINK_STS_DRAINING))
+    { /* likely, till we get to the end */ }
+    else if (nsNow <= pSink->nsDrainDeadline)
+    {
+        /* Have we drained the mixbuf now?  If so, update status and send drain
+           command to streams.  (As mentioned elsewhere we don't want to confuse
+           driver code by sending drain command while there is still data to write.) */
+        Assert((pSink->fStatus & AUDMIXSINK_STS_DIRTY) == (AudioMixBufUsed(&pSink->MixBuf) > 0 ? AUDMIXSINK_STS_DIRTY : 0));
+        if ((pSink->fStatus & (AUDMIXSINK_STS_DRAINED_MIXBUF | AUDMIXSINK_STS_DIRTY)) == 0)
+        {
+            LogFunc(("Sink '%s': Setting AUDMIXSINK_STS_DRAINED_MIXBUF and sending drain command to streams (after %RU64 ns).\n",
+                     pSink->pszName, nsNow - pSink->nsDrainStarted));
+            pSink->fStatus |= AUDMIXSINK_STS_DRAINED_MIXBUF;
+
+            RTListForEach(&pSink->lstStreams, pMixStream, AUDMIXSTREAM, Node)
+            {
+                pMixStream->pConn->pfnStreamControl(pMixStream->pConn, pMixStream->pStream, PDMAUDIOSTREAMCMD_DRAIN);
+            }
+        }
+
+        /* Check if all streams has stopped, and if so we stop the sink. */
         uint32_t const  cStreams         = pSink->cStreams;
         uint32_t        cStreamsDisabled = pSink->cStreams;
@@ -1857,7 +2016,24 @@
             }
         }
-        Log3Func(("[%s] pending disable: %u of %u disabled\n", pSink->pszName, cStreamsDisabled, cStreams));
-        if (cStreamsDisabled == cStreams)
-            audioMixerSinkReset(pSink);
+
+        if (cStreamsDisabled != cStreams)
+            Log3Func(("Sink '%s': %u out of %u streams disabled (after %RU64 ns).\n",
+                      pSink->pszName, cStreamsDisabled, cStreams, nsNow - pSink->nsDrainStarted));
+        else
+        {
+            LogFunc(("Sink '%s': All %u streams disabled. Drain done after %RU64 ns.\n",
+                     pSink->pszName, cStreamsDisabled, cStreams, nsNow - pSink->nsDrainStarted));
+            audioMixerSinkReset(pSink); /* clears the status */
+        }
+    }
+    else
+    {
+        /* Draining timed out. Just do an instant stop. */
+        LogFunc(("Sink '%s': pending disable timed out after %RU64 ns!\n", pSink->pszName, nsNow - pSink->nsDrainStarted));
+        RTListForEach(&pSink->lstStreams, pMixStream, AUDMIXSTREAM, Node)
+        {
+            pMixStream->pConn->pfnStreamControl(pMixStream->pConn, pMixStream->pStream, PDMAUDIOSTREAMCMD_DISABLE);
+        }
+        audioMixerSinkReset(pSink); /* clears the status */
     }
 
@@ -1874,4 +2050,5 @@
 {
     AssertPtrReturn(pSink, VERR_INVALID_POINTER);
+    Assert(pSink->uMagic == AUDMIXSINK_MAGIC);
     int rc = RTCritSectEnter(&pSink->CritSect);
     AssertRCReturn(rc, rc);
@@ -1908,4 +2085,5 @@
     PAUDMIXSINK pSink = (PAUDMIXSINK)pvUser;
     AssertPtr(pSink);
+    Assert(pSink->uMagic == AUDMIXSINK_MAGIC);
     RT_NOREF(hThreadSelf);
 
@@ -1919,5 +2097,5 @@
 
         RTCritSectEnter(&pSink->CritSect);
-        if (pSink->fStatus & (AUDMIXSINK_STS_RUNNING | AUDMIXSINK_STS_PENDING_DISABLE))
+        if (pSink->fStatus & (AUDMIXSINK_STS_RUNNING | AUDMIXSINK_STS_DRAINING))
         {
             /*
@@ -1945,5 +2123,5 @@
              * DMA timer as it has normally stopped running at this point.
              */
-            if (!(pSink->fStatus & AUDMIXSINK_STS_PENDING_DISABLE))
+            if (!(pSink->fStatus & AUDMIXSINK_STS_DRAINING))
             { /* likely */ }
             else
@@ -1989,4 +2167,5 @@
 {
     AssertPtrReturn(pSink, VERR_INVALID_POINTER);
+    Assert(pSink->uMagic == AUDMIXSINK_MAGIC);
     int rc = RTCritSectEnter(&pSink->CritSect);
     AssertRCReturn(rc, rc);
@@ -2068,4 +2247,5 @@
 {
     AssertPtrReturn(pSink, VERR_INVALID_POINTER);
+    Assert(pSink->uMagic == AUDMIXSINK_MAGIC);
     int rc = RTCritSectEnter(&pSink->CritSect);
     AssertRCReturn(rc, rc);
@@ -2119,6 +2299,8 @@
      */
     AssertReturn(pSink, offStream);
+    Assert(pSink->uMagic == AUDMIXSINK_MAGIC);
     AssertReturn(pCircBuf, offStream);
     Assert(RTCritSectIsOwner(&pSink->CritSect));
+    RT_NOREF(idStream);
 
     /*
@@ -2129,9 +2311,10 @@
     uint32_t       cbToTransfer       = RT_MIN(cbCircBufReadable, cbSinkWritable);
     /* Make sure that we always align the number of bytes when reading to the stream's PCM properties. */
-    cbToTransfer = PDMAudioPropsFloorBytesToFrame(&pSink->PCMProps, cbToTransfer);
-
-    Log3Func(("idStream=%#x: cbSinkWritable=%#RX32 cbCircBufReadable=%#RX32 -> cbToTransfer=%#RX32 @%#RX64\n",
+    uint32_t const cbToTransfer2      = cbToTransfer = PDMAudioPropsFloorBytesToFrame(&pSink->PCMProps, cbToTransfer);
+
+    Log3Func(("idStream=%u: cbSinkWritable=%#RX32 cbCircBufReadable=%#RX32 -> cbToTransfer=%#RX32 @%#RX64\n",
               idStream, cbSinkWritable, cbCircBufReadable, cbToTransfer, offStream));
-    RT_NOREF(idStream);
+    AssertMsg(!(pSink->fStatus & AUDMIXSINK_STS_DRAINING) || cbCircBufReadable == pSink->cbDmaLeftToDrain,
+              ("cbCircBufReadable=%#x cbDmaLeftToDrain=%#x\n", cbCircBufReadable, pSink->cbDmaLeftToDrain));
 
     /*
@@ -2149,5 +2332,5 @@
         Assert(cbWritten <= cbSrcBuf);
 
-        Log2Func(("idStream=%#x: %#RX32/%#zx bytes read @%#RX64\n", idStream, cbWritten, cbSrcBuf, offStream));
+        Log2Func(("idStream=%u: %#RX32/%#zx bytes read @%#RX64\n", idStream, cbWritten, cbSrcBuf, offStream));
 #ifdef VBOX_WITH_DTRACE
         VBOXDD_AUDIO_MIXER_SINK_AIO_OUT(idStream, cbWritten, offStream);
@@ -2166,4 +2349,28 @@
         cbToTransfer -= cbWritten;
     }
+
+    /*
+     * Advance drain status.
+     */
+    if (!(pSink->fStatus & AUDMIXSINK_STS_DRAINING))
+    { /* likely for most of the playback time ... */ }
+    else if (!(pSink->fStatus & AUDMIXSINK_STS_DRAINED_DMA))
+    {
+        if (cbToTransfer2 >= pSink->cbDmaLeftToDrain)
+        {
+            Assert(cbToTransfer2 == pSink->cbDmaLeftToDrain);
+            Log3Func(("idStream=%u/'%s': Setting AUDMIXSINK_STS_DRAINED_DMA.\n", idStream, pSink->pszName));
+            pSink->cbDmaLeftToDrain = 0;
+            pSink->fStatus         |= AUDMIXSINK_STS_DRAINED_DMA;
+        }
+        else
+        {
+            pSink->cbDmaLeftToDrain -= cbToTransfer2;
+            Log3Func(("idStream=%u/'%s': still %#x bytes left in the DMA buffer\n",
+                      idStream, pSink->pszName, pSink->cbDmaLeftToDrain));
+        }
+    }
+    else
+        Assert(cbToTransfer2 == 0);
 
     return offStream;
@@ -2191,4 +2398,5 @@
      */
     AssertReturn(pSink, offStream);
+    Assert(pSink->uMagic == AUDMIXSINK_MAGIC);
     AssertReturn(pCircBuf, offStream);
     Assert(RTCritSectIsOwner(&pSink->CritSect));
@@ -2204,5 +2412,5 @@
     cbToTransfer = PDMAudioPropsFloorBytesToFrame(&pSink->PCMProps, cbToTransfer);
 
-    Log3Func(("idStream=%#x: cbSinkReadable=%#RX32 cbCircBufWritable=%#RX32 -> cbToTransfer=%#RX32 @%#RX64\n",
+    Log3Func(("idStream=%u: cbSinkReadable=%#RX32 cbCircBufWritable=%#RX32 -> cbToTransfer=%#RX32 @%#RX64\n",
               idStream, cbSinkReadable, cbCircBufWritable, cbToTransfer, offStream));
     RT_NOREF(idStream);
@@ -2270,4 +2478,5 @@
 {
     AssertPtrReturn(pSink, VERR_INVALID_POINTER);
+    Assert(pSink->uMagic == AUDMIXSINK_MAGIC);
     return RTSemEventSignal(pSink->AIO.hEvent);
 }
@@ -2283,4 +2492,5 @@
 {
     AssertPtrReturn(pSink, VERR_INVALID_POINTER);
+    Assert(pSink->uMagic == AUDMIXSINK_MAGIC);
     return RTCritSectEnter(&pSink->CritSect);
 }
@@ -2296,4 +2506,5 @@
 {
     AssertPtrReturn(pSink, VERR_INVALID_POINTER);
+    Assert(pSink->uMagic == AUDMIXSINK_MAGIC);
     return RTCritSectTryEnter(&pSink->CritSect);
 }
@@ -2320,7 +2531,8 @@
  * @param   pVolMaster          Master volume to set.
  */
-static int audioMixerSinkUpdateVolume(PAUDMIXSINK pSink, const PPDMAUDIOVOLUME pVolMaster)
+static int audioMixerSinkUpdateVolume(PAUDMIXSINK pSink, PCPDMAUDIOVOLUME pVolMaster)
 {
     AssertPtrReturn(pSink,      VERR_INVALID_POINTER);
+    Assert(pSink->uMagic == AUDMIXSINK_MAGIC);
     AssertPtrReturn(pVolMaster, VERR_INVALID_POINTER);
 
@@ -2373,4 +2585,5 @@
 {
     AssertPtrReturn(pSink, VERR_INVALID_POINTER);
+    Assert(pSink->uMagic == AUDMIXSINK_MAGIC);
     RT_NOREF(enmOp);
     AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
@@ -2431,4 +2644,5 @@
 static int audioMixerStreamCtlInternal(PAUDMIXSTREAM pMixStream, PDMAUDIOSTREAMCMD enmCmd)
 {
+    Assert(pMixStream->uMagic == AUDMIXSTREAM_MAGIC);
     AssertPtrReturn(pMixStream->pConn, VERR_AUDIO_STREAM_NOT_READY);
     AssertPtrReturn(pMixStream->pStream, VERR_AUDIO_STREAM_NOT_READY);
@@ -2452,4 +2666,6 @@
 static int audioMixerStreamUpdateStatus(PAUDMIXSTREAM pMixStream)
 {
+    Assert(pMixStream->uMagic == AUDMIXSTREAM_MAGIC);
+
     /*
      * Reset the mixer status to start with.
@@ -2521,5 +2737,5 @@
 
 /**
- * Destroys a mixer stream, internal version.
+ * Destroys & frees a mixer stream, internal version.
  *
  * Worker for audioMixerSinkDestroyInternal and AudioMixerStreamDestroy.
@@ -2533,4 +2749,5 @@
 
     LogFunc(("%s\n", pMixStream->pszName));
+    Assert(pMixStream->uMagic == AUDMIXSTREAM_MAGIC);
 
     if (pMixStream->pConn) /* Stream has a connector interface present? */
@@ -2575,4 +2792,5 @@
         return;
 
+/** @todo r=bird: Wrng critsect for audioMixerSinkRemoveStreamInternal   */
     int rc2 = RTCritSectEnter(&pMixStream->CritSect);
     AssertRC(rc2);
Index: trunk/src/VBox/Devices/Audio/AudioMixer.h
===================================================================
--- trunk/src/VBox/Devices/Audio/AudioMixer.h	(revision 88987)
+++ trunk/src/VBox/Devices/Audio/AudioMixer.h	(revision 88991)
@@ -42,8 +42,8 @@
 typedef struct AUDIOMIXER
 {
+    /** Magic value (AUDIOMIXER_MAGIC). */
+    uintptr_t               uMagic;
     /** The mixer's name. */
     char                   *pszName;
-    /** The mixer's critical section. */
-    RTCRITSECT              CritSect;
     /** The master volume of this mixer. */
     PDMAUDIOVOLUME          VolMaster;
@@ -54,7 +54,14 @@
     /** Mixer flags. See AUDMIXER_FLAGS_XXX. */
     uint32_t                fFlags;
+    /** The mixer's critical section. */
+    RTCRITSECT              CritSect;
 } AUDIOMIXER;
 /** Pointer to an audio mixer instance. */
 typedef AUDIOMIXER *PAUDIOMIXER;
+
+/** Value for AUDIOMIXER::uMagic. (Attilio Joseph "Teo" Macero)  */
+#define AUDIOMIXER_MAGIC                UINT32_C(0x19251030)
+/** Value for AUDIOMIXER::uMagic after destruction. */
+#define AUDIOMIXER_MAGIC_DEAD           UINT32_C(0x20080219)
 
 /** @name AUDMIXER_FLAGS_XXX - For AudioMixerCreate().
@@ -77,10 +84,8 @@
     /** List entry on AUDMIXSINK::lstStreams. */
     RTLISTNODE              Node;
-    /** Name of this stream. */
-    char                   *pszName;
-    /** The statistics prefix. */
-    char                   *pszStatPrefix;
-    /** Sink this stream is attached to. */
-    PAUDMIXSINK             pSink;
+    /** Magic value (AUDMIXSTREAM_MAGIC). */
+    uint32_t                uMagic;
+    /** The backend buffer size in frames (for draining deadline calc). */
+    uint32_t                cFramesBackendBuffer;
     /** Stream status of type AUDMIXSTREAM_STATUS_. */
     uint32_t                fStatus;
@@ -91,4 +96,10 @@
      * from the mixer buffer and to the drivers. */
     bool                    fUnreliable;
+    /** Name of this stream. */
+    char                   *pszName;
+    /** The statistics prefix. */
+    char                   *pszStatPrefix;
+    /** Sink this stream is attached to. */
+    PAUDMIXSINK             pSink;
     /** Pointer to audio connector being used. */
     PPDMIAUDIOCONNECTOR     pConn;
@@ -104,4 +115,10 @@
 /** Pointer to an audio mixer stream. */
 typedef AUDMIXSTREAM *PAUDMIXSTREAM;
+
+/** Value for AUDMIXSTREAM::uMagic. (Jan Erik Kongshaug)  */
+#define AUDMIXSTREAM_MAGIC                UINT32_C(0x19440704)
+/** Value for AUDMIXSTREAM::uMagic after destruction. */
+#define AUDMIXSTREAM_MAGIC_DEAD           UINT32_C(0x20191105)
+
 
 /** @name AUDMIXSTREAM_STATUS_XXX - mixer stream status.
@@ -133,14 +150,23 @@
     /** List entry on AUDIOMIXER::lstSinks. */
     RTLISTNODE              Node;
+    /** Magic value (AUDMIXSINK_MAGIC). */
+    uint32_t                uMagic;
+    /** The sink direction (either PDMAUDIODIR_IN or PDMAUDIODIR_OUT). */
+    PDMAUDIODIR             enmDir;
     /** Pointer to mixer object this sink is bound to. */
     PAUDIOMIXER             pParent;
     /** Name of this sink. */
     char                   *pszName;
-    /** The sink direction (either PDMAUDIODIR_IN or PDMAUDIODIR_OUT). */
-    PDMAUDIODIR             enmDir;
     /** The sink's PCM format (i.e. the guest device side). */
     PDMAUDIOPCMPROPS        PCMProps;
     /** Sink status bits - AUDMIXSINK_STS_XXX. */
     uint32_t                fStatus;
+    /** Number of bytes to be transferred from the device DMA buffer before the
+     *  streams will be put into draining mode. */
+    uint32_t                cbDmaLeftToDrain;
+    /** The deadline for draining if it's pending. */
+    uint64_t                nsDrainDeadline;
+    /** When the draining startet (for logging). */
+    uint64_t                nsDrainStarted;
     /** Number of streams assigned. */
     uint8_t                 cStreams;
@@ -215,4 +241,10 @@
 } AUDMIXSINK;
 
+/** Value for AUDMIXSINK::uMagic. (Sir George Martin)  */
+#define AUDMIXSINK_MAGIC                UINT32_C(0x19260103)
+/** Value for AUDMIXSINK::uMagic after destruction. */
+#define AUDMIXSINK_MAGIC_DEAD           UINT32_C(0x20160308)
+
+
 /** @name AUDMIXSINK_STS_XXX - Sink status bits.
  * @{ */
@@ -221,6 +253,10 @@
 /** The sink is active and running. */
 #define AUDMIXSINK_STS_RUNNING               RT_BIT(0)
-/** The sink is in a pending disable state. */
-#define AUDMIXSINK_STS_PENDING_DISABLE       RT_BIT(1)
+/** Draining the buffers and pending stop - output only. */
+#define AUDMIXSINK_STS_DRAINING              RT_BIT(1)
+/** Drained the DMA buffer. */
+#define AUDMIXSINK_STS_DRAINED_DMA           RT_BIT(2)
+/** Drained the mixer buffer, only waiting for streams (drivers) now. */
+#define AUDMIXSINK_STS_DRAINED_MIXBUF        RT_BIT(3)
 /** Dirty flag.
  * - For output sinks this means that there is data in the sink which has not
@@ -228,5 +264,5 @@
  * - For input sinks this means that there is data in the sink which has been
  *   recorded but not transferred to the destination yet. */
-#define AUDMIXSINK_STS_DIRTY                 RT_BIT(2)
+#define AUDMIXSINK_STS_DIRTY                 RT_BIT(4)
 /** @} */
 
@@ -258,5 +294,6 @@
 int     AudioMixerSinkCreateStream(PAUDMIXSINK pSink, PPDMIAUDIOCONNECTOR pConnector, PPDMAUDIOSTREAMCFG pCfg,
                                    PPDMDEVINS pDevIns, PAUDMIXSTREAM *ppStream);
-int     AudioMixerSinkEnable(PAUDMIXSINK pSink, bool fEnable);
+int     AudioMixerSinkStart(PAUDMIXSINK pSink);
+int     AudioMixerSinkDrainAndStop(PAUDMIXSINK pSink, uint32_t cbComming);
 void AudioMixerSinkDestroy(PAUDMIXSINK pSink, PPDMDEVINS pDevIns);
 uint32_t AudioMixerSinkGetReadable(PAUDMIXSINK pSink);
Index: trunk/src/VBox/Devices/Audio/DevHdaStream.cpp
===================================================================
--- trunk/src/VBox/Devices/Audio/DevHdaStream.cpp	(revision 88987)
+++ trunk/src/VBox/Devices/Audio/DevHdaStream.cpp	(revision 88991)
@@ -905,18 +905,23 @@
     if (pSink)
     {
-        if (fEnable && pStreamR3->State.pAioRegSink != pSink)
+        if (fEnable)
         {
-            if (pStreamR3->State.pAioRegSink)
+            if (pStreamR3->State.pAioRegSink != pSink)
             {
-                rc = AudioMixerSinkRemoveUpdateJob(pStreamR3->State.pAioRegSink, hdaR3StreamUpdateAsyncIoJob, pStreamR3);
-                AssertRC(rc);
+                if (pStreamR3->State.pAioRegSink)
+                {
+                    rc = AudioMixerSinkRemoveUpdateJob(pStreamR3->State.pAioRegSink, hdaR3StreamUpdateAsyncIoJob, pStreamR3);
+                    AssertRC(rc);
+                }
+                rc = AudioMixerSinkAddUpdateJob(pSink, hdaR3StreamUpdateAsyncIoJob, pStreamR3,
+                                                pStreamShared->State.Cfg.Device.cMsSchedulingHint);
+                AssertLogRelRC(rc);
+                pStreamR3->State.pAioRegSink = RT_SUCCESS(rc) ? pSink : NULL;
             }
-            rc = AudioMixerSinkAddUpdateJob(pSink, hdaR3StreamUpdateAsyncIoJob, pStreamR3,
-                                            pStreamShared->State.Cfg.Device.cMsSchedulingHint);
-            AssertLogRelRC(rc);
-            pStreamR3->State.pAioRegSink = RT_SUCCESS(rc) ? pSink : NULL;
+            rc = AudioMixerSinkStart(pSink);
         }
-        if (RT_SUCCESS(rc))
-            rc = AudioMixerSinkEnable(pSink, fEnable);
+        else
+            rc = AudioMixerSinkDrainAndStop(pSink,
+                                            pStreamR3->State.pCircBuf ? (uint32_t)RTCircBufUsed(pStreamR3->State.pCircBuf) : 0);
     }
     if (   RT_SUCCESS(rc)
Index: trunk/src/VBox/Devices/Audio/DevIchAc97.cpp
===================================================================
--- trunk/src/VBox/Devices/Audio/DevIchAc97.cpp	(revision 88987)
+++ trunk/src/VBox/Devices/Audio/DevIchAc97.cpp	(revision 88991)
@@ -974,12 +974,14 @@
             }
         }
+
+        if (RT_SUCCESS(rc))
+            rc = AudioMixerSinkStart(pSink);
     }
     else
+    {
         rc = ichac97R3StreamClose(pStream);
-
-    if (RT_SUCCESS(rc))
-    {
-        /* First, enable or disable the stream and the stream's sink, if any. */
-        rc = AudioMixerSinkEnable(pSink, fEnable);
+        if (RT_SUCCESS(rc))
+            rc = AudioMixerSinkDrainAndStop(pSink,
+                                            pStreamCC->State.pCircBuf ? (uint32_t)RTCircBufUsed(pStreamCC->State.pCircBuf) : 0);
     }
 
Index: trunk/src/VBox/Devices/Audio/DevSB16.cpp
===================================================================
--- trunk/src/VBox/Devices/Audio/DevSB16.cpp	(revision 88987)
+++ trunk/src/VBox/Devices/Audio/DevSB16.cpp	(revision 88991)
@@ -2045,7 +2045,15 @@
     }
 
-    /* First, enable or disable the stream and the stream's sink. */
-    rc = AudioMixerSinkEnable(pSink, fEnable);
-    AssertRCReturn(rc, rc);
+    /* Tell the mixer. */
+    if (fEnable)
+    {
+        rc = AudioMixerSinkStart(pSink);
+        AssertRCReturn(rc, rc);
+    }
+    else
+    {
+        rc = AudioMixerSinkDrainAndStop(pSink, pStream->State.pCircBuf ? (uint32_t)RTCircBufUsed(pStream->State.pCircBuf) : 0);
+        AssertRCReturn(rc, rc);
+    }
 
     pStream->State.fEnabled = fEnable;
Index: trunk/src/VBox/Devices/Audio/DrvAudio.cpp
===================================================================
--- trunk/src/VBox/Devices/Audio/DrvAudio.cpp	(revision 88987)
+++ trunk/src/VBox/Devices/Audio/DrvAudio.cpp	(revision 88991)
@@ -251,5 +251,5 @@
             /** The current pre-buffering read offset. */
             uint32_t            offPreBuf;
-            /** Number of bytes we've prebuffered. */
+            /** Number of bytes we've pre-buffered. */
             uint32_t            cbPreBuffered;
             /** The pre-buffering threshold expressed in bytes. */
@@ -363,11 +363,4 @@
     RTREQPOOL               hReqPool;
 
-    /** Handle to the disable-iteration timer. */
-    TMTIMERHANDLE           hTimer;
-    /** Set if hTimer is armed. */
-    bool volatile           fTimerArmed;
-    /** Unique name for the the disable-iteration timer.  */
-    char                    szTimerName[23];
-
 #ifdef VBOX_WITH_AUDIO_ENUM
     /** Handle to the timer for delayed re-enumeration of backend devices. */
@@ -547,10 +540,23 @@
     {
         PDMHOSTAUDIOSTREAMSTATE enmState = pThis->pHostDrvAudio->pfnStreamGetState(pThis->pHostDrvAudio, pStreamEx->pBackend);
-        Assert(enmState > PDMHOSTAUDIOSTREAMSTATE_INVALID && enmState < PDMHOSTAUDIOSTREAMSTATE_END);
         Log9Func(("%s: %s\n", pStreamEx->Core.szName, PDMHostAudioStreamStateGetName(enmState) ));
+        Assert(   enmState > PDMHOSTAUDIOSTREAMSTATE_INVALID
+               && enmState < PDMHOSTAUDIOSTREAMSTATE_END
+               && (enmState != PDMHOSTAUDIOSTREAMSTATE_DRAINING || pStreamEx->Guest.Cfg.enmDir == PDMAUDIODIR_OUT));
         return enmState;
     }
     Log9Func(("%s: not-working\n", pStreamEx->Core.szName));
     return PDMHOSTAUDIOSTREAMSTATE_NOT_WORKING;
+}
+
+
+/**
+ * Worker for drvAudioStreamProcessBackendStateChange that completes draining.
+ */
+DECLINLINE(void) drvAudioStreamProcessBackendStateChangeWasDraining(PDRVAUDIOSTREAM pStreamEx)
+{
+    Log(("drvAudioStreamProcessBackendStateChange: Stream '%s': Done draining - disabling stream.\n", pStreamEx->Core.szName));
+    pStreamEx->fStatus &= ~(PDMAUDIOSTREAM_STS_ENABLED | PDMAUDIOSTREAM_STS_PENDING_DISABLE);
+    drvAudioStreamResetInternal(pStreamEx);
 }
 
@@ -583,5 +589,8 @@
 
         case PDMHOSTAUDIOSTREAMSTATE_NOT_WORKING:
-            /* The stream has stopped working.  Switch to noplay mode. */
+        case PDMHOSTAUDIOSTREAMSTATE_INACTIVE:
+            /* The stream has stopped working or is inactive.  Switch stop any draining & to noplay mode. */
+            if (pStreamEx->fStatus & PDMAUDIOSTREAM_STS_PENDING_DISABLE)
+                drvAudioStreamProcessBackendStateChangeWasDraining(pStreamEx);
             if (enmDir == PDMAUDIODIR_OUT)
                 pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_NOPLAY;
@@ -600,4 +609,10 @@
                        or not, resetting the stream state. */
                     drvAudioStreamResetInternal(pStreamEx);
+                    break;
+
+                case PDMHOSTAUDIOSTREAMSTATE_DRAINING:
+                    /* Complete the draining. May race the iterate code. */
+                    if (pStreamEx->fStatus & PDMAUDIOSTREAM_STS_PENDING_DISABLE)
+                        drvAudioStreamProcessBackendStateChangeWasDraining(pStreamEx);
                     break;
 
@@ -611,8 +626,7 @@
             break;
 
-        case PDMHOSTAUDIOSTREAMSTATE_INACTIVE:
-            /* Stream is now inactive. Switch to noplay mode. */
-            if (enmDir == PDMAUDIODIR_OUT)
-                pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_NOPLAY;
+        case PDMHOSTAUDIOSTREAMSTATE_DRAINING:
+            /* We do all we need to do when issuing the DRAIN command. */
+            Assert(pStreamEx->fStatus & PDMAUDIOSTREAM_STS_PENDING_DISABLE);
             break;
 
@@ -2420,5 +2434,6 @@
     {
         if (   (pStreamEx->fStatus & PDMAUDIOSTREAM_STS_BACKEND_READY /* don't really need this check, do we? */)
-            && enmBackendState == PDMHOSTAUDIOSTREAMSTATE_OKAY)
+            && (   enmBackendState == PDMHOSTAUDIOSTREAMSTATE_OKAY
+                || enmBackendState == PDMHOSTAUDIOSTREAMSTATE_DRAINING) )
         {
             /** @todo Backend will change to explicit methods here, so please don't simplify
@@ -2509,48 +2524,4 @@
 
 /**
- * @callback_method_impl{FNTMTIMERDRV}
- */
-static DECLCALLBACK(void) drvAudioEmergencyIterateTimer(PPDMDRVINS pDrvIns, TMTIMERHANDLE hTimer, void *pvUser)
-{
-    PDRVAUDIO pThis = PDMINS_2_DATA(pDrvIns, PDRVAUDIO);
-    RT_NOREF(hTimer, pvUser);
-    RTCritSectEnter(&pThis->CritSect);
-
-    /*
-     * Iterate any stream with the pending-disable flag set.
-     */
-    uint32_t        cMilliesToNext = 0;
-    PDRVAUDIOSTREAM pStreamEx, pStreamExNext;
-    RTListForEachSafe(&pThis->lstStreams, pStreamEx, pStreamExNext, DRVAUDIOSTREAM, ListEntry)
-    {
-        if (   pStreamEx->uMagic == DRVAUDIOSTREAM_MAGIC
-            && pStreamEx->cRefs >= 1)
-        {
-            if (pStreamEx->fStatus & PDMAUDIOSTREAM_STS_PENDING_DISABLE)
-            {
-                drvAudioStreamIterateInternal(pThis, pStreamEx);
-
-                if (pStreamEx->fStatus & PDMAUDIOSTREAM_STS_PENDING_DISABLE)
-                    cMilliesToNext = 10;
-            }
-        }
-    }
-
-    /*
-     * Re-arm the timer if we still got streams in the pending state.
-     */
-    if (cMilliesToNext)
-    {
-        pThis->fTimerArmed = true;
-        PDMDrvHlpTimerSetMillies(pDrvIns, pThis->hTimer, cMilliesToNext);
-    }
-    else
-        pThis->fTimerArmed = false;
-
-    RTCritSectLeave(&pThis->CritSect);
-}
-
-
-/**
  * Controls an audio stream.
  *
@@ -2578,7 +2549,18 @@
             if (!(pStreamEx->fStatus & PDMAUDIOSTREAM_STS_ENABLED))
             {
-                /* Is a pending disable outstanding? Then disable first. */
+                /* Are we still draining this stream? Then we must disable it first. */
                 if (pStreamEx->fStatus & PDMAUDIOSTREAM_STS_PENDING_DISABLE)
+                {
+                    LogFunc(("Stream '%s' is still draining - disabling...\n", pStreamEx->Core.szName));
                     rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DISABLE);
+                    AssertRC(rc);
+                    if (drvAudioStreamGetBackendState(pThis, pStreamEx) != PDMHOSTAUDIOSTREAMSTATE_DRAINING)
+                    {
+                        pStreamEx->fStatus &= ~(PDMAUDIOSTREAM_STS_ENABLED | PDMAUDIOSTREAM_STS_PENDING_DISABLE);
+                        drvAudioStreamResetInternal(pStreamEx);
+                        rc = VINF_SUCCESS;
+                    }
+                }
+
                 if (RT_SUCCESS(rc))
                 {
@@ -2597,4 +2579,6 @@
                                     pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_PREBUF;
                                 break;
+                            case PDMHOSTAUDIOSTREAMSTATE_DRAINING:
+                                AssertFailed();
                             case PDMHOSTAUDIOSTREAMSTATE_OKAY:
                                 pStreamEx->Out.enmPlayState = pStreamEx->Out.cbPreBufThreshold > 0
@@ -2629,35 +2613,8 @@
             if (pStreamEx->fStatus & PDMAUDIOSTREAM_STS_ENABLED)
             {
-                /*
-                 * For playback (output) streams first mark the host stream as pending disable,
-                 * so that the rest of the remaining audio data will be played first before
-                 * closing the stream.
-                 */
-                if (pStreamEx->Core.enmDir == PDMAUDIODIR_OUT)
-                {
-                    LogFunc(("[%s] Pending disable/pause\n", pStreamEx->Core.szName));
-                    pStreamEx->fStatus |= PDMAUDIOSTREAM_STS_PENDING_DISABLE;
-                    PDMAUDIOSTREAM_STS_ASSERT_VALID(pStreamEx->fStatus);
-
-                    /* Schedule a follow up timer to the pending-disable state.  We cannot rely
-                       on the device to provide further callouts to finish the state transition.
-                       10ms is taking out of thin air and may be too course grained, we should
-                       really consider the amount of unplayed buffer in the backend and what not... */
-                    if (!pThis->fTimerArmed)
-                    {
-                        LogFlowFunc(("Arming emergency pending-disable hack...\n"));
-                        int rc2 = PDMDrvHlpTimerSetMillies(pThis->pDrvIns, pThis->hTimer, 10 /*ms*/);
-                        AssertRC(rc2);
-                        pThis->fTimerArmed = true;
-                    }
-                }
-
-                /* Can we close the host stream as well (not in pending disable mode)? */
-                if (!(pStreamEx->fStatus & PDMAUDIOSTREAM_STS_PENDING_DISABLE))
-                {
-                    rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DISABLE);
-                    if (RT_SUCCESS(rc))
-                        drvAudioStreamResetOnDisable(pStreamEx);
-                }
+                rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DISABLE);
+                LogFunc(("DISABLE '%s': Backend DISABLE -> %Rrc\n", pStreamEx->Core.szName, rc));
+                if (RT_SUCCESS(rc)) /** @todo ignore this and reset it anyway? */
+                    drvAudioStreamResetOnDisable(pStreamEx);
             }
             break;
@@ -2688,4 +2645,72 @@
             break;
 
+        case PDMAUDIOSTREAMCMD_DRAIN:
+            /*
+             * Only for output streams and we don't want this command more than once.
+             */
+            AssertReturn(pStreamEx->Core.enmDir == PDMAUDIODIR_OUT, VERR_INVALID_FUNCTION);
+            AssertBreak(!(pStreamEx->fStatus & PDMAUDIOSTREAM_STS_PENDING_DISABLE));
+            if (pStreamEx->fStatus & PDMAUDIOSTREAM_STS_ENABLED)
+            {
+                rc = VERR_INTERNAL_ERROR_2;
+                switch (pStreamEx->Out.enmPlayState)
+                {
+                    case DRVAUDIOPLAYSTATE_PREBUF:
+                        if (pStreamEx->Out.cbPreBuffered > 0)
+                        {
+                            LogFunc(("DRAIN '%s': Initiating draining of pre-buffered data...\n",
+                                     pStreamEx->Core.szName, drvAudioPlayStateName(pStreamEx->Out.enmPlayState)));
+                            pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_PREBUF_COMMITTING;
+                            pStreamEx->fStatus |= PDMAUDIOSTREAM_STS_PENDING_DISABLE;
+                            PDMAUDIOSTREAM_STS_ASSERT_VALID(pStreamEx->fStatus);
+                            break;
+                        }
+                        RT_FALL_THROUGH();
+                    case DRVAUDIOPLAYSTATE_NOPLAY:
+                    case DRVAUDIOPLAYSTATE_PREBUF_SWITCHING:
+                    case DRVAUDIOPLAYSTATE_PREBUF_OVERDUE:
+                        LogFunc(("DRAIN '%s': Nothing to drain (enmPlayState=%s)\n",
+                                 pStreamEx->Core.szName, drvAudioPlayStateName(pStreamEx->Out.enmPlayState)));
+                        rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DISABLE);
+                        AssertRC(rc);
+                        drvAudioStreamResetOnDisable(pStreamEx);
+                        break;
+
+                    case DRVAUDIOPLAYSTATE_PLAY:
+                    case DRVAUDIOPLAYSTATE_PLAY_PREBUF:
+                        LogFunc(("DRAIN '%s': Initiating backend draining (enmPlayState=%s -> NOPLAY) ...\n",
+                                 pStreamEx->Core.szName, drvAudioPlayStateName(pStreamEx->Out.enmPlayState)));
+                        pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_NOPLAY;
+                        rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DRAIN);
+                        if (RT_SUCCESS(rc))
+                        {
+                            pStreamEx->fStatus |= PDMAUDIOSTREAM_STS_PENDING_DISABLE;
+                            PDMAUDIOSTREAM_STS_ASSERT_VALID(pStreamEx->fStatus);
+                        }
+                        else
+                        {
+                            LogFunc(("DRAIN '%s': Backend DRAIN failed with %Rrc, disabling the stream instead...\n",
+                                     pStreamEx->Core.szName));
+                            rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DISABLE);
+                            AssertRC(rc);
+                            drvAudioStreamResetOnDisable(pStreamEx);
+                        }
+                        break;
+
+                    case DRVAUDIOPLAYSTATE_PREBUF_COMMITTING:
+                        LogFunc(("DRAIN '%s': Initiating draining of pre-buffered data (already committing)...\n",
+                                 pStreamEx->Core.szName, drvAudioPlayStateName(pStreamEx->Out.enmPlayState)));
+                        pStreamEx->fStatus |= PDMAUDIOSTREAM_STS_PENDING_DISABLE;
+                        PDMAUDIOSTREAM_STS_ASSERT_VALID(pStreamEx->fStatus);
+                        break;
+
+                    /* no default */
+                    case DRVAUDIOPLAYSTATE_INVALID:
+                    case DRVAUDIOPLAYSTATE_END:
+                        AssertFailedBreak();
+                }
+            }
+            break;
+
         default:
             rc = VERR_NOT_IMPLEMENTED;
@@ -2992,7 +3017,9 @@
 
     /*
-     * Pending disable is really what we're here for.  This only happens to output streams.
-     */
-    int rc = VINF_SUCCESS;
+     * Pending disable is really what we're here for.
+     *
+     * This only happens to output streams.  We ASSUME the caller (MixerBuffer)
+     * implements a timeout on the draining, so we skip that here.
+     */
     if (!(pStreamEx->fStatus & PDMAUDIOSTREAM_STS_PENDING_DISABLE))
     { /* likely until we get to the end of the stream at least. */ }
@@ -3000,98 +3027,50 @@
     {
         AssertReturn(pStreamEx->Core.enmDir == PDMAUDIODIR_OUT, VINF_SUCCESS);
-        /** @todo Add a timeout to these proceedings.  A few times that of the reported
-         *        buffer size or something like that. */
 
         /*
-         * Check if we have any data we need to write to the backend, try
-         * move it now.
+         * Move pre-buffered samples to the backend.
          */
-        /** @todo r=bird: It is possible the device has data buffered (e.g.
-         *        internal DMA buffer (HDA) or mixing buffer (HDA + AC'97).  We're
-         *        not taking that into account here.  I also suspect that neither is
-         *        the device/mixer code, and that if the guest is too quick disabling
-         *        the stream, it will just remain there till the next time something
-         *        is played.  That means that this code and associated timer hack
-         *        should probably not be here at all. */
-        uint32_t cFramesLive = 0;
-        switch (pStreamEx->Out.enmPlayState)
-        {
-            case DRVAUDIOPLAYSTATE_PLAY:                /* Nothing prebuffered. */
-            case DRVAUDIOPLAYSTATE_PLAY_PREBUF:         /* Not pre-buffering for current device. */
-            case DRVAUDIOPLAYSTATE_PREBUF_OVERDUE:      /* Output device isn't ready, drop it. */ /** @todo check state? */
-            case DRVAUDIOPLAYSTATE_PREBUF_SWITCHING:    /* No output device yet. */
-            case DRVAUDIOPLAYSTATE_NOPLAY:
-            case DRVAUDIOPLAYSTATE_INVALID:
-            case DRVAUDIOPLAYSTATE_END:
-                /* no default, want warnings. */
-                break;
-            case DRVAUDIOPLAYSTATE_PREBUF:
-            case DRVAUDIOPLAYSTATE_PREBUF_COMMITTING:
-                if (pStreamEx->Out.cbPreBuffered > 0)
+        if (pStreamEx->Out.enmPlayState == DRVAUDIOPLAYSTATE_PREBUF_COMMITTING)
+        {
+            if (pStreamEx->Out.cbPreBuffered > 0)
+            {
+                uint32_t cbIgnored = 0;
+                drvAudioStreamPreBufComitting(pThis, pStreamEx, NULL, 0, &cbIgnored);
+                Log3Func(("Stream '%s': Transferred %#x bytes\n", pStreamEx->Core.szName, cbIgnored));
+            }
+            if (pStreamEx->Out.cbPreBuffered == 0)
+            {
+                Log3Func(("Stream '%s': No more pre-buffered data -> NOPLAY + backend DRAIN\n", pStreamEx->Core.szName));
+                pStreamEx->Out.enmPlayState = DRVAUDIOPLAYSTATE_NOPLAY;
+
+                int rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DRAIN);
+                if (RT_FAILURE(rc))
                 {
-                    /* Must check the backend state here first and only try commit the
-                       pre-buffered samples if the backend is in working order. */
-                    PDMHOSTAUDIOSTREAMSTATE const enmBackendState = drvAudioStreamGetBackendState(pThis, pStreamEx);
-                                                                     /* ^^^ (checks pThis->pHostDrvAudio != NULL too) */
-                    if (   (pStreamEx->fStatus & PDMAUDIOSTREAM_STS_BACKEND_READY)
-                        && enmBackendState == PDMHOSTAUDIOSTREAMSTATE_OKAY)
-                    {
-                        uint32_t cbIgnored = 0;
-                        drvAudioStreamPreBufComitting(pThis, pStreamEx, NULL, 0, &cbIgnored);
-                        cFramesLive = PDMAudioPropsBytesToFrames(&pStreamEx->Core.Props, pStreamEx->Out.cbPreBuffered);
-                    }
-                    else
-                        Log3Func(("[%s] Skipping committing pre-buffered samples (status: %#x backend: %s)!\n",
-                                  pStreamEx->Core.szName, pStreamEx->fStatus, PDMHostAudioStreamStateGetName(enmBackendState)));
-                }
-                break;
-        }
-        Log3Func(("[%s] cFramesLive=%RU32\n", pStreamEx->Core.szName, cFramesLive));
-        if (cFramesLive == 0)
-        {
-            /*
-             * Tell the backend to start draining the stream, that is,
-             * play the remaining buffered data and stop.
-             */
-            rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DRAIN);
-            if (rc == VERR_NOT_SUPPORTED) /* Not all backends support draining yet. */
-                rc = VINF_SUCCESS;
-            /** @todo r=bird: We could probably just skip this next check, as if drainig
-             *        failes, we should definitely try disable the stream.  Maybe the
-             *        host audio device was unplugged and we're leaving this stream in a
-             *        bogus state. */
-            if (RT_SUCCESS(rc))
-            {
-                /*
-                 * Before we disable the stream, check if the backend has finished playing the buffered data.
-                 */
-                uint32_t cbPending;
-                if (!pThis->pHostDrvAudio || !pThis->pHostDrvAudio->pfnStreamGetPending) /* Optional. */
-                    cbPending = 0;
-                else
-                {
-                    cbPending = pThis->pHostDrvAudio->pfnStreamGetPending(pThis->pHostDrvAudio, pStreamEx->pBackend);
-                    Log3Func(("[%s] cbPending=%RU32 (%#RX32)\n", pStreamEx->Core.szName, cbPending, cbPending));
-                }
-                if (cbPending == 0)
-                {
-                    /*
-                     * Okay, disable it.
-                     */
-                    LogFunc(("[%s] Disabling pending stream\n", pStreamEx->Core.szName));
+                    LogFunc(("Stream '%s': Backend DRAIN failed with %Rrc, disabling the stream instead...\n",
+                             pStreamEx->Core.szName));
                     rc = drvAudioStreamControlInternalBackend(pThis, pStreamEx, PDMAUDIOSTREAMCMD_DISABLE);
-                    if (RT_SUCCESS(rc))
-                    {
-                        pStreamEx->fStatus &= ~(PDMAUDIOSTREAM_STS_ENABLED | PDMAUDIOSTREAM_STS_PENDING_DISABLE);
-                        PDMAUDIOSTREAM_STS_ASSERT_VALID(pStreamEx->fStatus);
-                        drvAudioStreamResetInternal(pStreamEx);
-                    }
-                    /** @todo r=bird: This log entry sounds a rather fishy to be honest...  Any
-                     *        backend which would do that, or genuinely need this?  */
-                    else
-                        LogFunc(("[%s] Backend vetoed against closing pending input stream, rc=%Rrc\n", pStreamEx->Core.szName, rc));
+                    AssertRC(rc);
+                    drvAudioStreamResetOnDisable(pStreamEx);
                 }
             }
         }
+        else
+            Assert(pStreamEx->Out.enmPlayState == DRVAUDIOPLAYSTATE_NOPLAY);
+
+        /*
+         * Check the backend status to see if it's still draining and to
+         * update our status when it stops doing so.
+         */
+        PDMHOSTAUDIOSTREAMSTATE const enmBackendState = drvAudioStreamGetBackendState(pThis, pStreamEx);
+        if (enmBackendState == PDMHOSTAUDIOSTREAMSTATE_DRAINING)
+        {
+            uint32_t cbIgnored = 0;
+            pThis->pHostDrvAudio->pfnStreamPlay(pThis->pHostDrvAudio, pStreamEx->pBackend, NULL, 0, &cbIgnored);
+        }
+        else
+        {
+            LogFunc(("Stream '%s': Backend finished draining.\n", pStreamEx->Core.szName));
+            drvAudioStreamResetOnDisable(pStreamEx);
+        }
     }
 
@@ -3099,8 +3078,5 @@
     pStreamEx->nsLastIterated = RTTimeNanoTS();
 
-    if (RT_FAILURE(rc))
-        LogFunc(("[%s] Failed with %Rrc\n", pStreamEx->Core.szName, rc));
-
-    return rc;
+    return VINF_SUCCESS; /** @todo r=bird: What can the caller do with an error status here? */
 }
 
@@ -3234,7 +3210,9 @@
      */
     uint32_t cbWritable = 0;
-    DRVAUDIOPLAYSTATE const enmPlayMode = pStreamEx->Out.enmPlayState;
+    DRVAUDIOPLAYSTATE const       enmPlayMode     = pStreamEx->Out.enmPlayState;
+    PDMHOSTAUDIOSTREAMSTATE const enmBackendState = drvAudioStreamGetBackendState(pThis, pStreamEx);
     if (   PDMAudioStrmStatusCanWrite(pStreamEx->fStatus)
-        && pThis->pHostDrvAudio != NULL)
+        && pThis->pHostDrvAudio != NULL
+        && enmBackendState != PDMHOSTAUDIOSTREAMSTATE_DRAINING)
     {
         switch (enmPlayMode)
@@ -3246,5 +3224,5 @@
             case DRVAUDIOPLAYSTATE_PLAY_PREBUF:
                 Assert(pStreamEx->fStatus & PDMAUDIOSTREAM_STS_BACKEND_READY);
-                Assert(drvAudioStreamGetBackendState(pThis, pStreamEx) == PDMHOSTAUDIOSTREAMSTATE_OKAY /* potential unplug race */);
+                Assert(enmBackendState == PDMHOSTAUDIOSTREAMSTATE_OKAY /* potential unplug race */);
                 cbWritable = pThis->pHostDrvAudio->pfnStreamGetWritable(pThis->pHostDrvAudio, pStreamEx->pBackend);
                 break;
@@ -3284,5 +3262,5 @@
                    to move the data. */
                 Assert(pStreamEx->fStatus & PDMAUDIOSTREAM_STS_BACKEND_READY);
-                Assert(drvAudioStreamGetBackendState(pThis, pStreamEx) == PDMHOSTAUDIOSTREAMSTATE_OKAY /* potential unplug race */);
+                Assert(enmBackendState == PDMHOSTAUDIOSTREAMSTATE_OKAY /* potential unplug race */);
                 uint32_t const cbMin = PDMAudioPropsFramesToBytes(&pStreamEx->Core.Props, 8);
                 cbWritable = pThis->pHostDrvAudio->pfnStreamGetWritable(pThis->pHostDrvAudio, pStreamEx->pBackend);
@@ -3308,6 +3286,7 @@
 
     RTCritSectLeave(&pThis->CritSect);
-    Log3Func(("[%s] cbWritable=%RU32 (%RU64ms) enmPlayMode=%s\n", pStreamEx->Core.szName, cbWritable,
-              PDMAudioPropsBytesToMilli(&pStreamEx->Host.Cfg.Props, cbWritable), drvAudioPlayStateName(enmPlayMode) ));
+    Log3Func(("[%s] cbWritable=%RU32 (%RU64ms) enmPlayMode=%s enmBackendState=%s\n",
+              pStreamEx->Core.szName, cbWritable, PDMAudioPropsBytesToMilli(&pStreamEx->Host.Cfg.Props, cbWritable),
+              drvAudioPlayStateName(enmPlayMode), PDMHostAudioStreamStateGetName(enmBackendState) ));
     return cbWritable;
 }
@@ -3349,4 +3328,5 @@
                 && (enmDir == PDMAUDIODIR_IN ? pThis->In.fEnabled : pThis->Out.fEnabled)
                 && (   enmBackendState == PDMHOSTAUDIOSTREAMSTATE_OKAY
+                    || enmBackendState == PDMHOSTAUDIOSTREAMSTATE_DRAINING
                     || enmBackendState == PDMHOSTAUDIOSTREAMSTATE_INITIALIZING ))
                 enmState = enmDir == PDMAUDIODIR_IN ? PDMAUDIOSTREAMSTATE_ENABLED_READABLE : PDMAUDIOSTREAMSTATE_ENABLED_WRITABLE;
@@ -4858,16 +4838,4 @@
                               STAMUNIT_BYTES, "Total bytes read.");
 #endif
-
-    /*
-     * Create a timer to do finish closing output streams in PENDING_DISABLE state.
-     *
-     * The device won't call us again after it has disabled a the stream and this is
-     * a real problem for truely cyclic buffer backends like DSound which will just
-     * continue to loop and loop if not stopped.
-     */
-    RTStrPrintf(pThis->szTimerName, sizeof(pThis->szTimerName), "AudioIterate-%u", pDrvIns->iInstance);
-    rc = PDMDrvHlpTMTimerCreate(pDrvIns, TMCLOCK_VIRTUAL, drvAudioEmergencyIterateTimer, NULL /*pvUser*/,
-                                0 /*fFlags*/, pThis->szTimerName, &pThis->hTimer);
-    AssertRCReturn(rc, rc);
 
 #ifdef VBOX_WITH_AUDIO_ENUM
Index: trunk/src/VBox/Devices/Audio/DrvHostAudioAlsa.cpp
===================================================================
--- trunk/src/VBox/Devices/Audio/DrvHostAudioAlsa.cpp	(revision 88987)
+++ trunk/src/VBox/Devices/Audio/DrvHostAudioAlsa.cpp	(revision 88991)
@@ -56,4 +56,5 @@
 #include <alsa/asoundlib.h>
 #include <alsa/control.h> /* For device enumeration. */
+#include <alsa/version.h>
 #include "DrvHostAudioAlsaStubs.h"
 
@@ -1125,7 +1126,19 @@
 {
     RT_NOREF(pInterface);
-    AssertPtrReturn(pStream, PDMHOSTAUDIOSTREAMSTATE_INVALID);
-
-    return PDMHOSTAUDIOSTREAMSTATE_OKAY;
+    PALSAAUDIOSTREAM pStreamALSA = (PALSAAUDIOSTREAM)pStream;
+    AssertPtrReturn(pStreamALSA, PDMHOSTAUDIOSTREAMSTATE_INVALID);
+
+    PDMHOSTAUDIOSTREAMSTATE enmStreamState = PDMHOSTAUDIOSTREAMSTATE_OKAY;
+    snd_pcm_state_t         enmAlsaState   = snd_pcm_state(pStreamALSA->hPCM);
+    if (enmAlsaState == SND_PCM_STATE_DRAINING)
+        enmStreamState = PDMHOSTAUDIOSTREAMSTATE_DRAINING;
+#if (((SND_LIB_MAJOR) << 16) | ((SND_LIB_MAJOR) << 8) | (SND_LIB_SUBMINOR)) >= 0x10002 /* was added in 1.0.2 */
+    else if (enmAlsaState == SND_PCM_STATE_DISCONNECTED)
+        enmStreamState = PDMHOSTAUDIOSTREAMSTATE_NOT_WORKING;
+#endif
+
+    Log5Func(("Stream '%s': ALSA state=%s -> %s\n",
+              pStreamALSA->Cfg.szName, snd_pcm_state_name(enmAlsaState), PDMHostAudioStreamStateGetName(enmStreamState) ));
+    return enmStreamState;
 }
 
@@ -1274,9 +1287,15 @@
     AssertPtrReturn(pInterface, VERR_INVALID_POINTER);
     AssertPtrReturn(pStream, VERR_INVALID_POINTER);
-    AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
-    AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
     AssertPtrReturn(pcbWritten, VERR_INVALID_POINTER);
     Log4Func(("@%#RX64: pvBuf=%p cbBuf=%#x (%u) state=%s - %s\n", pStreamALSA->offInternal, pvBuf, cbBuf, cbBuf,
               snd_pcm_state_name(snd_pcm_state(pStreamALSA->hPCM)), pStreamALSA->Cfg.szName));
+    if (cbBuf)
+        AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
+    else
+    {
+        /* Fend off draining calls. */
+        *pcbWritten = 0;
+        return VINF_SUCCESS;
+    }
 
     /*
Index: trunk/src/VBox/Devices/Audio/DrvHostAudioDSound.cpp
===================================================================
--- trunk/src/VBox/Devices/Audio/DrvHostAudioDSound.cpp	(revision 88987)
+++ trunk/src/VBox/Devices/Audio/DrvHostAudioDSound.cpp	(revision 88991)
@@ -224,7 +224,4 @@
     /** The Direct Sound capturing interface. */
     LPDIRECTSOUNDCAPTURE8       pDSC;
-    /** A drain stop timer that makes sure a draining stream will be
-     *  properly stopped (seem the non-loop mode is a bit buggy). */
-    TMTIMERHANDLE               hDrainTimer;
     /** List of streams (DSOUNDSTREAM).
      * Requires CritSect ownership.  */
@@ -1963,13 +1960,7 @@
         if (pStreamDS->Out.pDSB)
         {
-            /* Don't stop draining buffers. They'll stop by themselves. */
-            if (pStreamDS->Out.fDrain)
-                LogFunc(("Stream '%s' is draining\n", pStreamDS->Cfg.szName));
-            else
-            {
-                rc = drvHostDSoundStreamStopPlayback(pThis, pStreamDS, true /*fReset*/);
-                if (RT_SUCCESS(rc))
-                    LogRel3(("DSound: Stopped playback on '%s'.\n", pStreamDS->Cfg.szName));
-            }
+            rc = drvHostDSoundStreamStopPlayback(pThis, pStreamDS, true /*fReset*/);
+            if (RT_SUCCESS(rc))
+                LogRel3(("DSound: Stopped playback on '%s'.\n", pStreamDS->Cfg.szName));
         }
     }
@@ -2097,65 +2088,4 @@
 
 /**
- * This is used by the timer function as well as when arming the timer.
- *
- * @param   pThis       The DSound host audio driver instance data.
- * @param   msNow       A current RTTimeMilliTS() value.
- */
-static void drvHostDSoundDrainTimerWorker(PDRVHOSTDSOUND pThis, uint64_t msNow)
-{
-    /*
-     * Go thru the stream list and look at draining streams.
-     */
-    uint64_t        msNext = UINT64_MAX;
-    RTCritSectEnter(&pThis->CritSect);
-    PDSOUNDSTREAM   pCur;
-    RTListForEach(&pThis->HeadStreams, pCur, DSOUNDSTREAM, ListEntry)
-    {
-        if (   pCur->Cfg.enmDir == PDMAUDIODIR_OUT
-            && pCur->Out.fDrain)
-        {
-            uint64_t const msCurDeadline = pCur->Out.msDrainDeadline;
-            if (msCurDeadline > 0 && msCurDeadline < msNext)
-            {
-                if (msCurDeadline > msNow)
-                    msNext = pCur->Out.msDrainDeadline;
-                else
-                {
-                    LogRel2(("DSound: Stopping draining of '%s' {%s} ...\n",
-                             pCur->Cfg.szName, drvHostDSoundStreamStatusString(pCur)));
-                    if (pCur->Out.pDSB)
-                    {
-                        HRESULT hrc = IDirectSoundBuffer8_Stop(pCur->Out.pDSB);
-                        if (FAILED(hrc))
-                            LogRelMax(64, ("DSound: Failed to stop draining stream '%s': %Rhrc\n", pCur->Cfg.szName, hrc));
-                    }
-                    pCur->Out.fDrain = false;
-                }
-            }
-        }
-    }
-
-    /*
-     * Re-arm the timer if necessary.
-     */
-    if (msNext != UINT64_MAX)
-        PDMDrvHlpTimerSetMillies(pThis->pDrvIns, pThis->hDrainTimer, msNext - msNow);
-    RTCritSectLeave(&pThis->CritSect);
-}
-
-
-/**
- * @callback_method_impl{FNTMTIMERDRV,
- * This is a hack to ensure that draining streams stop playing.}
- */
-static DECLCALLBACK(void) drvHostDSoundDrainStopHackTimer(PPDMDRVINS pDrvIns, TMTIMERHANDLE hTimer, void *pvUser)
-{
-    PDRVHOSTDSOUND pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTDSOUND);
-    RT_NOREF(hTimer, pvUser);
-    drvHostDSoundDrainTimerWorker(pThis, RTTimeMilliTS());
-}
-
-
-/**
  * @ interface_method_impl{PDMIHOSTAUDIO,pfnStreamDrain}
  */
@@ -2185,5 +2115,4 @@
                 pStreamDS->Out.msDrainDeadline = PDMAudioPropsBytesToMilli(&pStreamDS->Cfg.Props,  pStreamDS->cbBufSize) + msNow;
                 pStreamDS->Out.fDrain          = true;
-                drvHostDSoundDrainTimerWorker(pThis, msNow);
             }
             else
@@ -2416,6 +2345,12 @@
     AssertPtrReturn(pStreamDS, PDMHOSTAUDIOSTREAMSTATE_INVALID);
 
-    LogFlowFunc(("returns OKAY for '%s' {%s}\n", pStreamDS->Cfg.szName, drvHostDSoundStreamStatusString(pStreamDS)));
-    return PDMHOSTAUDIOSTREAMSTATE_OKAY;
+    if (   pStreamDS->Cfg.enmDir != PDMAUDIODIR_OUT
+        || !pStreamDS->Out.fDrain)
+    {
+        LogFlowFunc(("returns OKAY for '%s' {%s}\n", pStreamDS->Cfg.szName, drvHostDSoundStreamStatusString(pStreamDS)));
+        return PDMHOSTAUDIOSTREAMSTATE_OKAY;
+    }
+    LogFlowFunc(("returns DRAINING for '%s' {%s}\n", pStreamDS->Cfg.szName, drvHostDSoundStreamStatusString(pStreamDS)));
+    return PDMHOSTAUDIOSTREAMSTATE_DRAINING;
 }
 
@@ -2430,6 +2365,6 @@
     PDSOUNDSTREAM   pStreamDS = (PDSOUNDSTREAM)pStream;
     AssertPtrReturn(pStreamDS, 0);
-    AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
-    AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
+    if (cbBuf)
+        AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
     AssertPtrReturn(pcbWritten, VERR_INVALID_POINTER);
 
@@ -2529,4 +2464,18 @@
                      drvHostDSoundStreamStatusString(pStreamDS) ));
     }
+    else if (   pStreamDS->Out.fDrain
+             && RTTimeMilliTS() >= pStreamDS->Out.msDrainDeadline)
+    {
+        LogRel2(("DSound: Stopping draining of '%s' {%s} ...\n", pStreamDS->Cfg.szName, drvHostDSoundStreamStatusString(pStreamDS)));
+        if (pStreamDS->Out.pDSB)
+        {
+            HRESULT hrc = IDirectSoundBuffer8_Stop(pStreamDS->Out.pDSB);
+            if (FAILED(hrc))
+                LogRelMax(64, ("DSound: Failed to stop draining stream '%s': %Rhrc\n", pStreamDS->Cfg.szName, hrc));
+        }
+        pStreamDS->Out.fDrain = false;
+        pStreamDS->fEnabled   = false;
+    }
+
     return VINF_SUCCESS;
 }
@@ -2756,5 +2705,4 @@
     RTListInit(&pThis->HeadStreams);
     pThis->pDrvIns                   = pDrvIns;
-    pThis->hDrainTimer               = NIL_TMTIMERHANDLE;
     /* IBase */
     pDrvIns->IBase.pfnQueryInterface = drvHostDSoundQueryInterface;
@@ -2842,12 +2790,4 @@
         LogRel2(("DSound: Notification client is disabled (ver %#RX64)\n", RTSystemGetNtVersion()));
 #endif
-
-    /*
-     * Create a timer that helps making sure draining streams actually stop playing.
-     * (Seem dsound doesn't stop by itself in many cases.)
-     */
-    int rc = PDMDrvHlpTMTimerCreate(pDrvIns, TMCLOCK_REAL, drvHostDSoundDrainStopHackTimer, NULL /*pvUser*/, 0 /*fFlags*/,
-                                    "DSound drain", &pThis->hDrainTimer);
-    AssertRCReturn(rc, rc);
 
     /*
Index: trunk/src/VBox/Devices/Audio/DrvHostAudioOss.cpp
===================================================================
--- trunk/src/VBox/Devices/Audio/DrvHostAudioOss.cpp	(revision 88987)
+++ trunk/src/VBox/Devices/Audio/DrvHostAudioOss.cpp	(revision 88991)
@@ -461,4 +461,5 @@
     POSSAUDIOSTREAM pStreamOSS = (POSSAUDIOSTREAM)pStream;
 
+    /** @todo this might be a little optimisitic...   */
     pStreamOSS->fDraining = false;
 
@@ -494,11 +495,28 @@
     if (pStreamOSS->Cfg.enmDir == PDMAUDIODIR_IN)
         rc = VINF_SUCCESS; /** @todo apparently nothing to do here? */
-    else if (pStreamOSS->fDraining)
-    {
-        LogFlow(("Ignoring stream disable because we're draining...\n"));
-        rc = VINF_SUCCESS; /** @todo apparently nothing to do here? */
-    }
     else
     {
+        /*
+         * If we're still draining, try kick the thread before we try disable the stream.
+         */
+        if (pStreamOSS->fDraining)
+        {
+            LogFuncFlow(("Trying to cancel draining...\n"));
+            if (pStreamOSS->hThreadDrain != NIL_RTTHREAD)
+            {
+                RTThreadPoke(pStreamOSS->hThreadDrain);
+                rc = RTThreadWait(pStreamOSS->hThreadDrain, 1 /*ms*/, NULL);
+                if (RT_SUCCESS(rc) || rc == VERR_INVALID_HANDLE)
+                    pStreamOSS->fDraining = false;
+                else
+                    LogFuncFlow(("Failed to cancel draining (%Rrc)\n", rc));
+            }
+            else
+            {
+                LogFuncFlow(("Thread handle is NIL, so we can't be draining\n"));
+                pStreamOSS->fDraining = false;
+            }
+        }
+
         /** @todo Official documentation says this isn't the right way to stop playback.
          *        It may work in some implementations but fail in all others...  Suggest
@@ -710,6 +728,9 @@
 {
     RT_NOREF(pInterface);
-    AssertPtrReturn(pStream, PDMHOSTAUDIOSTREAMSTATE_INVALID);
-    return PDMHOSTAUDIOSTREAMSTATE_OKAY;
+    POSSAUDIOSTREAM pStreamOSS = (POSSAUDIOSTREAM)pStream;
+    AssertPtrReturn(pStreamOSS, PDMHOSTAUDIOSTREAMSTATE_INVALID);
+    if (!pStreamOSS->fDraining)
+        return PDMHOSTAUDIOSTREAMSTATE_OKAY;
+    return PDMHOSTAUDIOSTREAMSTATE_DRAINING;
 }
 
Index: trunk/src/VBox/Devices/Audio/DrvHostAudioPulseAudio.cpp
===================================================================
--- trunk/src/VBox/Devices/Audio/DrvHostAudioPulseAudio.cpp	(revision 88987)
+++ trunk/src/VBox/Devices/Audio/DrvHostAudioPulseAudio.cpp	(revision 88991)
@@ -1279,4 +1279,6 @@
             if (enmOpState == PA_OPERATION_RUNNING)
             {
+/** @todo consider corking it immediately instead, as that's what the caller
+ *        wants now... */
                 LogFlowFunc(("Drain (%p) already running on '%s', skipping.\n", pStreamPA->pDrainOp, pStreamPA->Cfg.szName));
                 pa_threaded_mainloop_unlock(pThis->pMainLoop);
@@ -1558,19 +1560,39 @@
     PDRVHOSTPULSEAUDIO pThis = RT_FROM_MEMBER(pInterface, DRVHOSTPULSEAUDIO, IHostAudio);
     AssertPtrReturn(pStream, PDMHOSTAUDIOSTREAMSTATE_INVALID);
+    PPULSEAUDIOSTREAM  pStreamPA = (PPULSEAUDIOSTREAM)pStream;
+    AssertPtrReturn(pStreamPA, PDMHOSTAUDIOSTREAMSTATE_INVALID);
 
     /* Check PulseAudio's general status. */
+    PDMHOSTAUDIOSTREAMSTATE enmBackendStreamState = PDMHOSTAUDIOSTREAMSTATE_NOT_WORKING;
     if (pThis->pContext)
     {
-        pa_context_state_t const enmState = pa_context_get_state(pThis->pContext);
-        if (PA_CONTEXT_IS_GOOD(enmState))
-        {
-            /** @todo should we check the actual stream state? */
-            return PDMHOSTAUDIOSTREAMSTATE_OKAY;
-        }
-        LogFunc(("non-good context state: %d\n", enmState));
+        pa_context_state_t const enmCtxState = pa_context_get_state(pThis->pContext);
+        if (PA_CONTEXT_IS_GOOD(enmCtxState))
+        {
+            pa_stream_state_t const enmStreamState = pa_stream_get_state(pStreamPA->pStream);
+            if (PA_STREAM_IS_GOOD(enmState))
+            {
+                if (enmState != PA_STREAM_CREATING)
+                {
+                    if (   pStreamPA->Cfg.enmDir != PDMAUDIODIR_OUT
+                        || pStreamPA->pDrainOp == NULL
+                        || pa_operation_get_state(pStreamPA->pDrainOp) != PA_OPERATION_RUNNING)
+                        enmBackendStreamState = PDMHOSTAUDIOSTREAMSTATE_OKAY;
+                    else
+                        enmBackendStreamState = PDMHOSTAUDIOSTREAMSTATE_DRAINING;
+                }
+                else
+                    enmBackendStreamState = PDMHOSTAUDIOSTREAMSTATE_INITIALIZING;
+            }
+            else
+                LogFunc(("non-good PA stream state: %d\n", enmStreamState));
+        }
+        else
+            LogFunc(("non-good PA context state: %d\n", enmCtxState));
     }
     else
         LogFunc(("No context!\n"));
-    return PDMHOSTAUDIOSTREAMSTATE_NOT_WORKING;
+    LogFlowFunc(("returns %s for stream '%s'\n", PDMHostAudioStreamStateGetName(enmBackendStreamState), pStreamPA->Cfg.szName));
+    return enmBackendStreamState;
 }
 
@@ -1585,7 +1607,13 @@
     PPULSEAUDIOSTREAM  pStreamPA = (PPULSEAUDIOSTREAM)pStream;
     AssertPtrReturn(pStreamPA, VERR_INVALID_POINTER);
-    AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
-    AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
     AssertPtrReturn(pcbWritten, VERR_INVALID_POINTER);
+    if (cbBuf)
+        AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
+    else
+    {
+        /* Fend off draining calls. */
+        *pcbWritten = 0;
+        return VINF_SUCCESS;
+    }
 
     pa_threaded_mainloop_lock(pThis->pMainLoop);
Index: trunk/src/VBox/Devices/Audio/DrvHostAudioWasApi.cpp
===================================================================
--- trunk/src/VBox/Devices/Audio/DrvHostAudioWasApi.cpp	(revision 88987)
+++ trunk/src/VBox/Devices/Audio/DrvHostAudioWasApi.cpp	(revision 88991)
@@ -252,7 +252,4 @@
     IMMDevice                      *pIDeviceOutput;
 
-    /** A drain stop timer that makes sure a draining stream will be properly
-     * stopped (mainly for clean state and to reduce resource usage). */
-    TMTIMERHANDLE                   hDrainTimer;
     /** List of streams (DRVHOSTAUDIOWASSTREAM).
      * Requires CritSect ownership.  */
@@ -2140,22 +2137,15 @@
 
     int rc = VINF_SUCCESS;
-    if (!pStreamWas->fDraining)
-    {
-        if (pStreamWas->fStarted)
-        {
-            HRESULT hrc = pStreamWas->pDevCfg->pIAudioClient->Stop();
-            LogFlowFunc(("Stop(%s) returns %Rhrc\n", pStreamWas->Cfg.szName, hrc));
-            if (FAILED(hrc))
-            {
-                LogRelMax(64, ("WasAPI: Stopping '%s' failed (disable): %Rhrc\n", pStreamWas->Cfg.szName, hrc));
-                rc = VERR_GENERAL_FAILURE;
-            }
-            pStreamWas->fStarted = false;
-        }
-    }
-    else
-    {
-        LogFunc(("Stream '%s' is still draining...\n", pStreamWas->Cfg.szName));
-        Assert(pStreamWas->fStarted);
+    if (pStreamWas->fStarted)
+    {
+        HRESULT hrc = pStreamWas->pDevCfg->pIAudioClient->Stop();
+        LogFlowFunc(("Stop(%s) returns %Rhrc\n", pStreamWas->Cfg.szName, hrc));
+        if (FAILED(hrc))
+        {
+            LogRelMax(64, ("WasAPI: Stopping '%s' failed (disable): %Rhrc\n", pStreamWas->Cfg.szName, hrc));
+            rc = VERR_GENERAL_FAILURE;
+        }
+        pStreamWas->fStarted  = false;
+        pStreamWas->fDraining = false;
     }
 
@@ -2241,78 +2231,9 @@
 
 /**
- * This is used by the timer function as well as when arming the timer.
- *
- * @param   pThis       The DSound host audio driver instance data.
- * @param   msNow       A current RTTimeMilliTS() value.
- */
-static void drvHostWasDrainTimerWorker(PDRVHOSTAUDIOWAS pThis, uint64_t msNow)
-{
-    /*
-     * Go thru the stream list and look at draining streams.
-     */
-    uint64_t        msNext = UINT64_MAX;
-    RTCritSectRwEnterShared(&pThis->CritSectStreamList);
-    PDRVHOSTAUDIOWASSTREAM   pCur;
-    RTListForEach(&pThis->StreamHead, pCur, DRVHOSTAUDIOWASSTREAM, ListEntry)
-    {
-        if (   pCur->fDraining
-            && pCur->Cfg.enmDir == PDMAUDIODIR_OUT)
-        {
-            Assert(pCur->fStarted);
-            uint64_t msCurDeadline = pCur->msDrainDeadline;
-            if (msCurDeadline > 0 && msCurDeadline < msNext)
-            {
-                /* Take the lock and recheck: */
-                RTCritSectEnter(&pCur->CritSect);
-                msCurDeadline = pCur->msDrainDeadline;
-                if (   pCur->fDraining
-                    && msCurDeadline > 0
-                    && msCurDeadline < msNext)
-                {
-                    if (msCurDeadline > msNow)
-                        msNext = pCur->msDrainDeadline;
-                    else
-                    {
-                        LogRel2(("WasAPI: Stopping draining of '%s' {%s} ...\n",
-                                 pCur->Cfg.szName, drvHostWasStreamStatusString(pCur)));
-                        HRESULT hrc = pCur->pDevCfg->pIAudioClient->Stop();
-                        if (FAILED(hrc))
-                            LogRelMax(64, ("WasAPI: Failed to stop draining stream '%s': %Rhrc\n", pCur->Cfg.szName, hrc));
-                        pCur->fDraining = false;
-                        pCur->fStarted  = false;
-                    }
-                }
-                RTCritSectLeave(&pCur->CritSect);
-            }
-        }
-    }
-
-    /*
-     * Re-arm the timer if necessary.
-     */
-    if (msNext != UINT64_MAX)
-        PDMDrvHlpTimerSetMillies(pThis->pDrvIns, pThis->hDrainTimer, msNext - msNow);
-    RTCritSectRwLeaveShared(&pThis->CritSectStreamList);
-}
-
-
-/**
- * @callback_method_impl{FNTMTIMERDRV,
- * This is to ensure that draining streams stop properly.}
- */
-static DECLCALLBACK(void) drvHostWasDrainStopTimer(PPDMDRVINS pDrvIns, TMTIMERHANDLE hTimer, void *pvUser)
-{
-    PDRVHOSTAUDIOWAS pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTAUDIOWAS);
-    RT_NOREF(hTimer, pvUser);
-    drvHostWasDrainTimerWorker(pThis, RTTimeMilliTS());
-}
-
-
-/**
  * @ interface_method_impl{PDMIHOSTAUDIO,pfnStreamDrain}
  */
 static DECLCALLBACK(int) drvHostAudioWasHA_StreamDrain(PPDMIHOSTAUDIO pInterface, PPDMAUDIOBACKENDSTREAM pStream)
 {
-    PDRVHOSTAUDIOWAS        pThis      = RT_FROM_MEMBER(pInterface, DRVHOSTAUDIOWAS, IHostAudio);
+    RT_NOREF(pInterface);
     PDRVHOSTAUDIOWASSTREAM  pStreamWas = (PDRVHOSTAUDIOWASSTREAM)pStream;
     AssertReturn(pStreamWas->Cfg.enmDir == PDMAUDIODIR_OUT, VERR_INVALID_PARAMETER);
@@ -2323,6 +2244,7 @@
     /*
      * If the stram was started, calculate when the buffered data has finished
-     * playing and switch to drain mode.  Use the drain timer callback worker
-     * to re-arm the timer or to stop the playback.
+     * playing and switch to drain mode.  DrvAudio will keep on calling
+     * pfnStreamPlay with an empty buffer while we're draining, so we'll use
+     * that for checking the deadline and finally stopping the stream.
      */
     RTCritSectEnter(&pStreamWas->CritSect);
@@ -2363,10 +2285,4 @@
     RTCritSectLeave(&pStreamWas->CritSect);
 
-    /*
-     * Always do drain timer processing to re-arm the timer or actually stop
-     * this stream (and others).  (Must be done _after_ unlocking the stream.)
-     */
-    drvHostWasDrainTimerWorker(pThis, RTTimeMilliTS());
-
     LogFlowFunc(("returns %Rrc {%s}\n", rc, drvHostWasStreamStatusString(pStreamWas)));
     return rc;
@@ -2537,5 +2453,13 @@
     {
         if (RT_SUCCESS(pStreamWas->pDevCfg->rcSetup))
-            enmState = PDMHOSTAUDIOSTREAMSTATE_OKAY;
+        {
+            if (!pStreamWas->fDraining)
+                enmState = PDMHOSTAUDIOSTREAMSTATE_OKAY;
+            else
+            {
+                Assert(pStreamWas->Cfg.enmDir == PDMAUDIODIR_OUT);
+                enmState = PDMHOSTAUDIOSTREAMSTATE_DRAINING;
+            }
+        }
         else if (   pStreamWas->pDevCfg->rcSetup == VERR_AUDIO_STREAM_INIT_IN_PROGRESS
                  || pStreamWas->fSwitchingDevice )
@@ -2563,7 +2487,7 @@
     PDRVHOSTAUDIOWASSTREAM  pStreamWas = (PDRVHOSTAUDIOWASSTREAM)pStream;
     AssertPtrReturn(pStreamWas, VERR_INVALID_POINTER);
-    AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
-    AssertReturn(cbBuf, VERR_INVALID_PARAMETER);
     AssertPtrReturn(pcbWritten, VERR_INVALID_POINTER);
+    if (cbBuf)
+        AssertPtrReturn(pvBuf, VERR_INVALID_POINTER);
     Assert(PDMAudioPropsIsSizeAligned(&pStreamWas->Cfg.Props, cbBuf));
 
@@ -2615,5 +2539,5 @@
         uint32_t const cFramesToWrite = PDMAudioPropsBytesToFrames(&pStreamWas->Cfg.Props, cbToWrite);
         Assert(PDMAudioPropsFramesToBytes(&pStreamWas->Cfg.Props, cFramesToWrite) == cbToWrite);
-        Log3Func(("@%RX64: cFramesPending=%#x -> cbWritable=%#x cbToWrite=%#x cFramesToWrite=%#x {%s}\n",
+        Log3Func(("@%#RX64: cFramesPending=%#x -> cbWritable=%#x cbToWrite=%#x cFramesToWrite=%#x {%s}\n",
                   pStreamWas->offInternal, cFramesPending, cbWritable, cbToWrite, cFramesToWrite,
                   drvHostWasStreamStatusString(pStreamWas) ));
@@ -2673,8 +2597,24 @@
 
     /*
+     * Do draining deadline processing.
+     */
+    uint64_t const msNow = RTTimeMilliTS();
+    if (   !pStreamWas->fDraining
+        || msNow < pStreamWas->msDrainDeadline)
+    { /* likely */ }
+    else
+    {
+        LogRel2(("WasAPI: Stopping draining of '%s' {%s} ...\n", pStreamWas->Cfg.szName, drvHostWasStreamStatusString(pStreamWas)));
+        HRESULT hrc = pStreamWas->pDevCfg->pIAudioClient->Stop();
+        if (FAILED(hrc))
+            LogRelMax(64, ("WasAPI: Failed to stop draining stream '%s': %Rhrc\n", pStreamWas->Cfg.szName, hrc));
+        pStreamWas->fDraining = false;
+        pStreamWas->fStarted  = false;
+    }
+
+    /*
      * Done.
      */
     uint64_t const msPrev = pStreamWas->msLastTransfer;
-    uint64_t const msNow  = RTTimeMilliTS();
     if (cbWritten)
         pStreamWas->msLastTransfer = msNow;
@@ -2996,5 +2936,4 @@
      */
     pThis->pDrvIns                          = pDrvIns;
-    pThis->hDrainTimer                      = NIL_TMTIMERHANDLE;
     pThis->hEvtCachePurge                   = NIL_RTSEMEVENTMULTI;
 #if 0
@@ -3139,11 +3078,4 @@
 
     pThis->pNotifyClient->lockLeave();
-
-    /*
-     * We need a timer for draining streams.
-     */
-    rc = PDMDrvHlpTMTimerCreate(pDrvIns, TMCLOCK_REAL, drvHostWasDrainStopTimer, NULL /*pvUser*/, 0 /*fFlags*/,
-                                "WasAPI drain", &pThis->hDrainTimer);
-    AssertRCReturn(rc, rc);
 
 #if 0
