media/libsydneyaudio/sydney_os2_moz.patch
author Mitchell Field <mitchell.field@live.com.au>
Wed, 26 Jan 2011 07:14:30 +1100
changeset 63450 261f1a85206a2cc3ddc483d6d86e388c3c6ca1c6
parent 42942 b860cc41eb96cf8d88cb09748a27b3257d18400d
permissions -rw-r--r--
Bug 462361 - Remove support for building with Cygwin. r=khuey

diff --git a/media/libsydneyaudio/src/sydney_audio_os2.c b/media/libsydneyaudio/src/sydney_audio_os2.c
--- a/media/libsydneyaudio/src/sydney_audio_os2.c
+++ b/media/libsydneyaudio/src/sydney_audio_os2.c
@@ -44,64 +44,60 @@
  *  interrupt that stream, the sound device may run out of data.  While
  *  it should simply pause until more data is available, on some machines
  *  a buffer underrun causes the device to stop responding and to ignore
  *  new data until an MCI_STOP or MCI_PAUSE command is issued.
  *  
  *  The solution used here is to track the number of buffers in use and
  *  to pause the device when the count falls below a threshold.  Writing
  *  a new buffer to the device causes playback to resume automatically.
- *  To support this scheme, the code uses 2 event semaphores to pass
- *  buffer counts between its two threads (the app's decode thread and
- *  DART's event thread).  It also has the event thread do as little as
- *  possible to ensure it's not busy when a buffer-free event occurs.
+ *  To support this scheme, the code uses atomic operations on 2 counters
+ *  to pass buffer counts between its two threads (the app's decode thread
+ *  and DART's event thread).  It also has the event thread do as little
+ *  as possible to ensure it's not busy when a buffer-free event occurs.
  *
  */
 /*****************************************************************************/
 
 #include <stdlib.h>
 #include <stdio.h>
 #include <string.h>
 #include <stdarg.h>
 #include "sydney_audio.h"
 
 #define INCL_DOS
 #define INCL_MCIOS2
 #include <os2.h>
 #include <os2me.h>
+#include <386/builtin.h>
 
 /*****************************************************************************/
 
 /* this will have to be changed to a variable
  * if other than 16-bit samples are ever supported */
 #define SAOS2_SAMPLE_SIZE   2
 
-/* the number of buffers to allocate - the ogg decoder typically
- * writes 8k at a time, so this works out to roughly 1/2 second */
-#define SAOS2_BUF_CNT       11
-
-/* this could be as large as 65535 but making it smaller helps
- * avoid having the DART event thread think it's running out of
- * buffers if the decoder sends larger chunks of data less often */
-#define SAOS2_RAW_BUFSIZE   16384
+/* the number of buffers to allocate;  each buffer requires
+ * 64kb of linear address space in the low-mem private arena;
+ * actual physical memory used depends on each buffer's size */
+#define SAOS2_BUF_CNT       40
 
-/* playback states */
-#define SAOS2_INIT          0
-#define SAOS2_RECOVER       1
-#define SAOS2_PLAY          2
-#define SAOS2_EXIT          3
+/* the minimum number of milliseconds worth of data required before
+ * a buffer is written to the device - the actual number of ms per
+ * write will usually be greater;  the size of each buffer is based
+ * on this figure and the stream's rate & number of channels */
+#define SAOS2_MS_PER_WRITE  40
 
-/* an indefinite wait invites a hung thread */
-#define SAOS2_SEM_WAIT      5000
+/* if the number of buffers in use is less than this value,
+ * os2_mixer_event() will pause the device to prevent an underrun */
+#define SAOS2_UNDERRUN_CNT  2
 
-/* the only 2 return codes we care about */
-#ifndef INCL_DOSERRORS
-#define ERROR_ALREADY_POSTED    299
-#define ERROR_ALREADY_RESET     300
-#endif
+/* wait 5 seconds for a buffer to become free -
+ * an indefinite wait invites a hung thread */
+#define SAOS2_WAIT          5000
 
 /*****************************************************************************/
 /*  Debug  */
 
 #ifdef DEBUG
   #ifndef SAOS2_ERROR
     #define SAOS2_ERROR
   #endif
@@ -120,52 +116,64 @@
 struct sa_stream {
 
   /* audio format info */
   const char *      client_name;
   sa_mode_t         mode;
   sa_pcm_format_t   format;
   uint32_t          rate;
   uint32_t          nchannels;
+  uint32_t          bps;
 
   /* device info */
   uint16_t          hwDeviceID;
   uint32_t          hwMixHandle;
   PMIXERPROC        hwWriteProc;
 
   /* buffer allocations */
   int32_t           bufCnt;
   size_t            bufSize;
+  size_t            bufMin;
   PMCI_MIX_BUFFER   bufList;
 
   /* buffer usage tracking */
-  HEV               freeSem;
+  volatile uint32_t freeNew;
   int32_t           freeCnt;
   int32_t           freeNdx;
-  int32_t           readyCnt;
-  int32_t           readyNdx;
-  HEV               usedSem;
-  volatile int32_t  usedCnt;
+  volatile uint32_t usedNew;
+  int32_t           usedCnt;
+  int32_t           usedMin;
 
   /* miscellaneous */
-  volatile int32_t  state;
+  volatile uint32_t playing;
+  volatile uint32_t writeTime;
+  volatile uint32_t writeNew;
   int64_t           writePos;
 };
 
 /*****************************************************************************/
 /*  Private (static) Functions  */
 
 static int32_t  os2_mixer_event(uint32_t ulStatus, PMCI_MIX_BUFFER pBuffer,
                                 uint32_t ulFlags);
-static int      os2_write_to_device(sa_stream_t *s);
 static void     os2_stop_device(uint16_t hwDeviceID);
 static int      os2_pause_device(uint16_t hwDeviceID, uint32_t release);
 static int      os2_get_free_count(sa_stream_t *s, int32_t count);
 
 /*****************************************************************************/
+/*  Mozilla-specific Additions  */
+
+/* load mdm.dll on demand */
+static int      os2_load_mdm(void);
+
+/* invoke mciSendCommand() via a static variable */
+typedef ULONG _System     MCISENDCOMMAND(USHORT, USHORT, ULONG, PVOID, USHORT);
+static MCISENDCOMMAND *   _mciSendCommand = 0;
+
+/*****************************************************************************/
 /*  Sydney Audio Functions                                                   */
 /*****************************************************************************/
 
 /** Normal way to open a PCM device */
 
 int     sa_stream_create_pcm(sa_stream_t **  s, 
                              const char *    client_name, 
                              sa_mode_t       mode, 
@@ -176,16 +184,20 @@ int     sa_stream_create_pcm(sa_stream_t
   uint32_t      status = SA_SUCCESS;
   uint32_t      size;
   uint32_t      rc;
   sa_stream_t * sTemp = 0;
 
   /* this do{}while(0) "loop" makes it easy to ensure
    * resources are freed on exit if there's an error */
 do {
+  /* load mdm.dll if it isn't already loaded */
+  if (os2_load_mdm() != SA_SUCCESS)
+    return SA_ERROR_SYSTEM;
+
   if (mode != SA_MODE_WRONLY || format != SA_PCM_FORMAT_S16_LE)
     return os2_error(SA_ERROR_NOT_SUPPORTED, "sa_stream_create_pcm",
                      "invalid mode or format", 0);
 
   if (!s)
     return os2_error(SA_ERROR_INVALID, "sa_stream_create_pcm",
                      "s is null", 0);
   *s = 0;
@@ -199,49 +211,45 @@ do {
     status = os2_error(SA_ERROR_OOM, "sa_stream_create_pcm",
                        "DosAllocMem - rc=", rc);
     break;
   }
 
   memset(sTemp, 0, size);
   sTemp->bufList = (PMCI_MIX_BUFFER)&sTemp[1];
 
-  /* set the number of buffers;  round the buffer
-   * size down to the nearest multiple of a frame; */
-  sTemp->bufCnt  = SAOS2_BUF_CNT;
-  sTemp->bufSize = SAOS2_RAW_BUFSIZE - 
-                   (SAOS2_RAW_BUFSIZE % (SAOS2_SAMPLE_SIZE * nchannels));
-
-  /* create event semaphores to signal free buffers */
-  rc = DosCreateEventSem(0, &sTemp->freeSem, 0, FALSE);
-  if (!rc)
-    rc = DosCreateEventSem(0, &sTemp->usedSem, 0, FALSE);
-  if (rc) {
-    status = os2_error(SA_ERROR_SYSTEM, "sa_stream_create_pcm",
-                       "DosCreateEventSem - rc=", rc);
-    break;
-  }
-
   /* fill in the miscellanea */
   sTemp->client_name = client_name;
   sTemp->mode        = mode;
   sTemp->format      = format;
   sTemp->rate        = rate;
   sTemp->nchannels   = nchannels;
+  sTemp->bps         = rate * nchannels * SAOS2_SAMPLE_SIZE;
+
+  /* each buffer requires 64k of linear address space;
+   * the actual physical memory used is much less */
+  sTemp->bufCnt  = SAOS2_BUF_CNT;
+
+  /* a buffer must contain at least 'bufmin' bytes before it's written
+   * to the device - this equates to SAOS2_MS_PER_WRITE worth of data */
+  sTemp->bufMin  = (sTemp->bps * SAOS2_MS_PER_WRITE) / 1000;
+
+  /* 'bufSize' is 150% of 'bufmin' rounded up to the nearest page
+   * boundary, then rounded down to a multiple of the frame size;
+   * this ensures that all data delivered to sa_stream_write() will
+   * fit in a single buffer & that all committed memory can be used */
+  sTemp->bufSize = (((3 * sTemp->bufMin) / 2) + 0xfff) & ~0xfff;
+  sTemp->bufSize -= sTemp->bufSize % (SAOS2_SAMPLE_SIZE * nchannels);
 
   *s = sTemp;
 
 } while (0);
 
   /* on error, free any allocations */
   if (status != SA_SUCCESS && sTemp) {
-    if (sTemp->freeSem)
-      DosCloseEventSem(sTemp->freeSem);
-    if (sTemp->usedSem)
-      DosCloseEventSem(sTemp->usedSem);
     if (sTemp)
       DosFreeMem(sTemp);
   }
 
   return status;
 }
 
 /*****************************************************************************/
@@ -265,17 +273,17 @@ do {
   /* s->bufCnt will be restored after successfully allocating buffers */
   bufCntRequested = s->bufCnt;
   s->bufCnt = 0;
 
   /* open the Amp-Mixer using the default device in shared mode */
   memset(&AmpOpenParms, 0, sizeof(MCI_AMP_OPEN_PARMS));
   AmpOpenParms.pszDeviceType = (PSZ)(MCI_DEVTYPE_AUDIO_AMPMIX | 0);
 
-  rc = mciSendCommand(0, MCI_OPEN,
+  rc = _mciSendCommand(0, MCI_OPEN,
                       MCI_WAIT | MCI_OPEN_TYPE_ID | MCI_OPEN_SHAREABLE,
                       (void*)&AmpOpenParms, 0);
   if (LOUSHORT(rc)) {
     status = os2_error(SA_ERROR_NO_DEVICE, "sa_stream_open",
                        "MCI_OPEN - rc=", LOUSHORT(rc));
     break;
   }
 
@@ -287,17 +295,17 @@ do {
   MixSetupParms.ulBitsPerSample = 16;
   MixSetupParms.ulFormatTag     = MCI_WAVE_FORMAT_PCM;
   MixSetupParms.ulFormatMode    = MCI_PLAY;
   MixSetupParms.ulSamplesPerSec = s->rate;
   MixSetupParms.ulChannels      = s->nchannels;
   MixSetupParms.ulDeviceType    = MCI_DEVTYPE_WAVEFORM_AUDIO;
   MixSetupParms.pmixEvent       = (MIXEREVENT*)os2_mixer_event;
 
-  rc = mciSendCommand(s->hwDeviceID, MCI_MIXSETUP,
+  rc = _mciSendCommand(s->hwDeviceID, MCI_MIXSETUP,
                       MCI_WAIT | MCI_MIXSETUP_INIT,
                       (void*)&MixSetupParms, 0);
   if (LOUSHORT(rc)) {
     status = os2_error(SA_ERROR_NOT_SUPPORTED, "sa_stream_open",
                        "MCI_MIXSETUP - rc=", LOUSHORT(rc));
     break;
   }
 
@@ -306,31 +314,32 @@ do {
   s->hwWriteProc = MixSetupParms.pmixWrite;
 
   /* allocate device buffers from the Amp-Mixer */
   BufferParms.ulStructLength = sizeof(MCI_BUFFER_PARMS);
   BufferParms.ulNumBuffers   = bufCntRequested;
   BufferParms.ulBufferSize   = s->bufSize;
   BufferParms.pBufList       = s->bufList;
 
-  rc = mciSendCommand(s->hwDeviceID, MCI_BUFFER,
+  rc = _mciSendCommand(s->hwDeviceID, MCI_BUFFER,
                       MCI_WAIT | MCI_ALLOCATE_MEMORY,
                       (void*)&BufferParms, 0);
   if (LOUSHORT(rc)) {
     status = os2_error(SA_ERROR_OOM, "sa_stream_open",
                        "MCI_ALLOCATE_MEMORY - rc=", LOUSHORT(rc));
     break;
   }
 
   /* MCI_ALLOCATE_MEMORY may have decreased the,
    * number of buffers, so update the counts */
   s->bufCnt  = BufferParms.ulNumBuffers;
   s->freeCnt = BufferParms.ulNumBuffers;
 
   /* sa_stream_write() & os2_mixer_event() require these initializations */
+  s->usedMin = SAOS2_UNDERRUN_CNT;
   for (ctr = 0; ctr < s->bufCnt; ctr++) {
     s->bufList[ctr].ulStructLength = sizeof(MCI_MIX_BUFFER);
     s->bufList[ctr].ulBufferLength = 0;
     s->bufList[ctr].ulUserParm = (uint32_t)s;
   }
 
 } while (0);
 
@@ -350,50 +359,57 @@ int     sa_stream_destroy(sa_stream_t *s
 
   if (!s)
     return os2_error(SA_ERROR_NO_INIT, "sa_stream_destroy", "s is null", 0);
 
   /* if the device was opened, close it */
   if (s->hwDeviceID) {
 
     /* prevent os2_mixer_event() from reacting to a buffer under-run */
-    s->state = SAOS2_EXIT;
+    s->bufMin = 0;
+    s->playing = FALSE;
+
+    /* If another instance has already acquired the device the
+     * MCI commands below will fail, so re-acquire it temporarily.
+     * MCI_CLOSE will release the device to the previous owner. */
+    rc = _mciSendCommand(s->hwDeviceID, MCI_ACQUIREDEVICE,
+                         MCI_WAIT,
+                         (void*)&GenericParms, 0);
+    if (LOUSHORT(rc))
+      os2_error(0, "sa_stream_destroy",
+                "MCI_ACQUIREDEVICE - rc=", LOUSHORT(rc));
 
     /* stop the device (which may not actually be playing) */
     os2_stop_device(s->hwDeviceID);
 
     /* if hardware buffers were allocated, free them */
     if (s->bufCnt) {
       BufferParms.hwndCallback   = 0;
       BufferParms.ulStructLength = sizeof(MCI_BUFFER_PARMS);
       BufferParms.ulNumBuffers   = s->bufCnt;
       BufferParms.ulBufferSize   = s->bufSize;
       BufferParms.pBufList       = s->bufList;
 
-      rc = mciSendCommand(s->hwDeviceID, MCI_BUFFER,
+      rc = _mciSendCommand(s->hwDeviceID, MCI_BUFFER,
                           MCI_WAIT | MCI_DEALLOCATE_MEMORY,
                           (void*)&BufferParms, 0);
       if (LOUSHORT(rc))
         status = os2_error(SA_ERROR_SYSTEM, "sa_stream_destroy",
                            "MCI_DEALLOCATE_MEMORY - rc=", LOUSHORT(rc));
     }
 
-    rc = mciSendCommand(s->hwDeviceID, MCI_CLOSE,
+    rc = _mciSendCommand(s->hwDeviceID, MCI_CLOSE,
                         MCI_WAIT,
                         (void*)&GenericParms, 0);
     if (LOUSHORT(rc))
       status = os2_error(SA_ERROR_SYSTEM, "sa_stream_destroy",
                          "MCI_CLOSE - rc=", LOUSHORT(rc));
   }
 
   /* free other resources we allocated */
-  if (s->freeSem)
-    DosCloseEventSem(s->freeSem);
-  if (s->usedSem)
-    DosCloseEventSem(s->usedSem);
   DosFreeMem(s);
 
   return status;
 }
 
 /*****************************************************************************/
 
 /** Interleaved playback function */
@@ -411,61 +427,95 @@ int     sa_stream_write(sa_stream_t * s,
 
   /* exit if no data */
   if (!nbytes)
     return SA_SUCCESS;
 
   /* This should only loop on the last write before sa_stream_drain()
    * is called;  at other times, 'nbytes' won't exceed 'bufSize'. */
   while (nbytes) {
+    size_t  offs;
+    size_t  left;
 
     /* get the count of free buffers, wait until at least one
      * is available (in practice, this should never block) */
     if (os2_get_free_count(s, 1))
       return SA_ERROR_SYSTEM;
 
     /* copy as much as will fit into the buffer */
     pHW = &(s->bufList[s->freeNdx]);
-    cnt = (nbytes > s->bufSize) ? s->bufSize : nbytes;
-    memcpy(pHW->pBuffer, (char*)data, cnt);
-    pHW->ulBufferLength = cnt;
+
+    offs = pHW->ulBufferLength;
+    left = s->bufSize - offs;
+    cnt = (nbytes > left) ? left : nbytes;
+    memcpy(&((char*)pHW->pBuffer)[offs], (char*)data, cnt);
+
+    pHW->ulBufferLength += cnt;
     nbytes -= cnt;
     data = (char*)data + cnt;
 
-    /* adjust cnts & indices, then send the buffer to the device */
+    /* don't dispatch the buffer until it has bufMin bytes */
+    if (pHW->ulBufferLength < s->bufMin)
+      continue;
+
+    /* write the buffer to the device */
+    rc = s->hwWriteProc(s->hwMixHandle, pHW, 1);
+    if (LOUSHORT(rc)) {
+      pHW->ulBufferLength = 0;
+      return os2_error(SA_ERROR_SYSTEM, "sa_stream_write",
+                       "mixWrite - rc=", LOUSHORT(rc));
+    }
+
+    /* signal the event thread that a new buffer is now in use */
+    __atomic_increment(&s->usedNew);
+    s->playing = TRUE;
+
     s->freeCnt--;
     s->freeNdx = (s->freeNdx + 1) % s->bufCnt;
-    s->readyCnt++;
-    if (os2_write_to_device(s))
-      return SA_ERROR_SYSTEM;
   }
 
   return SA_SUCCESS;
 }
 
 /*****************************************************************************/
 
 /** sync/timing */
 
 int     sa_stream_get_position(sa_stream_t *s, sa_position_t position, int64_t *pos)
 {
   uint32_t      rc;
+  uint32_t      then;
+  uint32_t      now;
 
   if (!s || !pos)
     return os2_error(SA_ERROR_NO_INIT, "sa_stream_get_position",
                      "s or pos is null", 0);
 
   if (position != SA_POSITION_WRITE_SOFTWARE)
     return os2_error(SA_ERROR_NOT_SUPPORTED, "sa_stream_get_position",
                      "unsupported postion type=", position);
 
-  /* this is the nbr of bytes that are known to have been played
-   * already; the MCI command to get stream position isn't usable - 
-   * it returns a time value that resets when the stream is paused */
-  *pos = s->writePos;
+  /* Return the count of bytes that are known to have been played
+   * already plus an adjustment for the number that may have been
+   * played since the last mixer event.  Since both 'writePos' and
+   * 'writeTime' are volatile, the loop ensures both are in sync.
+   * Note:  the MCI command to get stream position isn't usable - 
+   * it returns a time value that resets when the stream is paused. */
+
+  do {
+    then = s->writeTime;
+    s->writePos += __atomic_xchg(&s->writeNew, 0);
+    *pos = s->writePos;
+
+    /* adjust if device is playing & there's been at least one write */
+    if (s->playing && s->writePos) {
+      DosQuerySysInfo(QSV_MS_COUNT, QSV_MS_COUNT, &now, sizeof(now));
+      *pos += ((now - then) * s->bps) / 1000;
+    }
+  } while (then != s->writeTime);
 
   return SA_SUCCESS;
 }
 
 /*****************************************************************************/
 
 /** Resume playing after a pause */
 
@@ -473,75 +523,86 @@ int     sa_stream_resume(sa_stream_t *s)
 {
   uint32_t          rc;
   MCI_GENERIC_PARMS GenericParms = { 0 };
 
   if (!s)
     return os2_error(SA_ERROR_NO_INIT, "sa_stream_resume",
                      "s is null", 0);
 
-  rc = mciSendCommand(s->hwDeviceID, MCI_ACQUIREDEVICE,
+  rc = _mciSendCommand(s->hwDeviceID, MCI_ACQUIREDEVICE,
                       MCI_WAIT,
                       (void*)&GenericParms, 0);
   if (LOUSHORT(rc))
     return os2_error(SA_ERROR_SYSTEM, "sa_stream_resume",
                      "MCI_ACQUIREDEVICE - rc=", LOUSHORT(rc));
 
-  rc = mciSendCommand(s->hwDeviceID, MCI_RESUME,
+  /* this may produce a spurious error if the device
+   * was just acquired, so report it but ignore it */
+  rc = _mciSendCommand(s->hwDeviceID, MCI_RESUME,
                       MCI_WAIT,
                       (void*)&GenericParms, 0);
   if (LOUSHORT(rc))
-    return os2_error(SA_ERROR_SYSTEM, "sa_stream_resume",
-                     "MCI_RESUME - rc=", LOUSHORT(rc));
+    os2_error(SA_ERROR_SYSTEM, "sa_stream_resume",
+              "MCI_RESUME - rc=", LOUSHORT(rc));
+
+  /* reset the last write time so get_position() doesn't over-adjust */
+  DosQuerySysInfo(QSV_MS_COUNT, QSV_MS_COUNT,
+                  (void*)&s->writeTime, sizeof(s->writeTime));
+  s->playing = TRUE;
 
   return SA_SUCCESS;
 }
 
 /*****************************************************************************/
 
 /** Pause audio playback (do not empty the buffer) */
 
 int     sa_stream_pause(sa_stream_t *s)
 {
   if (!s)
     return os2_error(SA_ERROR_NO_INIT, "sa_stream_pause", "s is null", 0);
 
   /* pause & release device */
+  s->playing = FALSE;
   return os2_pause_device(s->hwDeviceID, TRUE);
 }
 
 /*****************************************************************************/
 
 /** Block until all audio has been played */
 
 int     sa_stream_drain(sa_stream_t *s)
 {
   int       status = SA_SUCCESS;
   char      buf[32];
 
   if (!s)
     return os2_error(SA_ERROR_NO_INIT, "sa_stream_drain", "s is null", 0);
 
   /* keep os2_mixer_event() from reacting to buffer under-runs */
-  s->state = SAOS2_EXIT;
+  s->usedMin = 0;
+
+  /* perform the smallest possible write to force any
+   * partially-filled buffer to be written to the device */
+  memset(buf, 0, sizeof(buf));
+  s->bufMin = 0;
+  sa_stream_write(s, buf, s->nchannels * SAOS2_SAMPLE_SIZE);
 
   /* DART won't start playing until 2 buffers have been written,
-   * so write a dummy 2nd buffer if any buffers are in use */
-  if (s->freeCnt < SAOS2_BUF_CNT) {
-    memset(buf, 0, sizeof(buf));
+   * so write a dummy 2nd buffer if writePos is still zero */
+  if (!s->writePos)
+    s->writePos += __atomic_xchg(&s->writeNew, 0);
+  if (!s->writePos)
     sa_stream_write(s, buf, s->nchannels * SAOS2_SAMPLE_SIZE);
-  }
-
-  /* write all remaining buffers to the device */
-  if (s->readyCnt)
-    status = os2_write_to_device(s);
 
   /* wait for all buffers to become free */
   if (!status)
     status = os2_get_free_count(s, s->bufCnt);
+  s->playing = FALSE;
 
   /* stop the device so it doesn't misbehave due to an under-run */
   os2_stop_device(s->hwDeviceID);
 
   return status;
 }
 
 /*****************************************************************************/
@@ -556,19 +617,17 @@ int     sa_stream_get_write_size(sa_stre
 
   /* return a non-zero value here in case the upstream code ignores
    * the return code - if so, sa_stream_write() will fail instead */
   if (os2_get_free_count(s, 0)) {
     *size = s->bufSize;
     return SA_ERROR_SYSTEM;
   }
 
-  /* limiting each write to a single buffer
-   * produces smoother results in some cases */
-  *size = s->freeCnt ? s->bufSize : 0;
+  *size = s->freeCnt * s->bufSize;
 
   return SA_SUCCESS;
 }
 
 /*****************************************************************************/
 
 /** set absolute volume using a value ranging from 0.0 to 1.0 */
 
@@ -581,17 +640,17 @@ int     sa_stream_set_volume_abs(sa_stre
     return os2_error(SA_ERROR_NO_INIT, "sa_stream_set_volume_abs",
                      "s is null", 0);
 
   /* convert f.p. value to an integer value ranging
    * from 0 to 100 and apply to both channels */
   SetParms.ulLevel = (vol * 100);
   SetParms.ulAudio = MCI_SET_AUDIO_ALL;
 
-  rc = mciSendCommand(s->hwDeviceID, MCI_SET,
+  rc = _mciSendCommand(s->hwDeviceID, MCI_SET,
                       MCI_WAIT | MCI_SET_AUDIO | MCI_SET_VOLUME,
                       (void*)&SetParms, 0);
   if (LOUSHORT(rc))
     return os2_error(SA_ERROR_SYSTEM, "sa_stream_set_volume_abs",
                      "MCI_SET_VOLUME - rc=", LOUSHORT(rc));
 
   return SA_SUCCESS;
 }
@@ -608,17 +667,17 @@ int     sa_stream_get_volume_abs(sa_stre
 
   if (!s || !vol)
     return os2_error(SA_ERROR_NO_INIT, "sa_stream_get_volume_abs",
                      "s or vol is null", 0);
 
   memset(&StatusParms, 0, sizeof(MCI_STATUS_PARMS));
   StatusParms.ulItem = MCI_STATUS_VOLUME;
 
-  rc = mciSendCommand(s->hwDeviceID, MCI_STATUS,
+  rc = _mciSendCommand(s->hwDeviceID, MCI_STATUS,
                       MCI_WAIT | MCI_STATUS_ITEM,
                       (void*)&StatusParms, 0);
   if (LOUSHORT(rc)) {
     /* if there's an error, return a reasonable value */
     StatusParms.ulReturn = (50 | 50 << 16);
     status = os2_error(SA_ERROR_SYSTEM, "sa_stream_get_volume_abs",
                        "MCI_STATUS_VOLUME - rc=", LOUSHORT(rc));
   }
@@ -638,118 +697,70 @@ int     sa_stream_get_volume_abs(sa_stre
 /*****************************************************************************/
 
 /** signal the decode thread that a buffer is available -
  ** this runs on a separate high-priority thread created by DART */
 
 static int32_t os2_mixer_event(uint32_t ulStatus, PMCI_MIX_BUFFER pBuffer,
                                uint32_t ulFlags)
 {
-  uint32_t      rc;
-  int32_t       posted;
   sa_stream_t * s;
 
   /* check for errors */
   if (ulFlags & MIX_STREAM_ERROR)
-    rc = os2_error(0, "os2_mixer_event", "MIX_STREAM_ERROR - status=", ulStatus);
+    os2_error(0, "os2_mixer_event", "MIX_STREAM_ERROR - status=", ulStatus);
 
   if (!(ulFlags & MIX_WRITE_COMPLETE))
     return os2_error(TRUE, "os2_mixer_event",
                      "unexpected event - flag=", ulFlags);
 
   if (!pBuffer || !pBuffer->ulUserParm)
     return os2_error(TRUE, "os2_mixer_event", "null pointer", 0);
 
   /* Note: this thread doesn't use a mutex to avoid a deadlock with the one
    * DART uses to prevent MCI operations while this function is running */
   s = (sa_stream_t *)pBuffer->ulUserParm;
 
   /* update the number of buffers that are now in use */
-  rc = DosResetEventSem(s->usedSem, (unsigned long*)&posted);
-  if (rc && rc != ERROR_ALREADY_RESET) {
-    posted = 0;
-    rc = os2_error(rc, "os2_mixer_event", "DosResetEventSem - rc=", rc);
-  }
-  s->usedCnt += posted - 1;
+  s->usedCnt += __atomic_xchg(&s->usedNew, 0);
+  s->usedCnt--;
 
   /* if fewer than 2 buffers are in use, enter recovery mode -
    * if we wait until they're all free, it's often too late; */
-  if (s->usedCnt < 2 && s->state == SAOS2_PLAY) {
-    s->state = SAOS2_RECOVER;
+  if (s->usedCnt < s->usedMin) {
+    s->playing = FALSE;
     os2_pause_device(s->hwDeviceID, FALSE);
-    rc = os2_error(rc, "os2_mixer_event",
-                   "too few buffers in use - recovering", 0);
+    os2_error(0, "os2_mixer_event",
+              "too few buffers in use - recovering", 0);
   }
 
-  /* setting the write position after the buffer has been played yields
-   * far more accurate timing than setting it in sa_stream_write() */
-  s->writePos = s->writePos + (int64_t)pBuffer->ulBufferLength;
+  /* pass the number of newly played bytes to the other thread;
+   * get the time so the other thread can estimate how many
+   * additional bytes have been consumed since this event */
+  __atomic_add(&s->writeNew, pBuffer->ulBufferLength);
   pBuffer->ulBufferLength = 0;
+  DosQuerySysInfo(QSV_MS_COUNT, QSV_MS_COUNT,
+                  (void*)&s->writeTime, sizeof(s->writeTime));
 
   /* signal the decode thread that a buffer is available */
-  rc = DosPostEventSem(s->freeSem);
-  if (rc && rc != ERROR_ALREADY_POSTED)
-    rc = os2_error(rc, "os2_mixer_event", "DosPostEventSem - rc=", rc);
+  __atomic_increment(&s->freeNew);
 
   return TRUE;
 }
 
 /*****************************************************************************/
 
-/** write as many buffers as available to the device */
-
-static int  os2_write_to_device(sa_stream_t *s)
-{
-  uint32_t      rc;
-  int32_t       cnt;
-  int32_t       ctr;
-
-  /* this executes twice if bufList wraps, otherwise just once */
-  while (s->readyCnt) {
-
-    /* deal with wrap */
-    cnt = (s->readyNdx + s->readyCnt > s->bufCnt) ?
-          (s->bufCnt - s->readyNdx) : s->readyCnt;
-
-    /* if the write fails, abort */
-    rc = s->hwWriteProc(s->hwMixHandle, &(s->bufList[s->readyNdx]), cnt);
-    if (LOUSHORT(rc))
-      return os2_error(SA_ERROR_SYSTEM, "os2_write_to_device",
-                       "mixWrite - rc=", LOUSHORT(rc));
-
-    /* signal the event thread that 'cnt' buffers are now in use */
-    for (ctr = 0; ctr < cnt; ctr++) {
-      rc = DosPostEventSem(s->usedSem);
-      if (rc && rc != ERROR_ALREADY_POSTED)
-        return os2_error(SA_ERROR_SYSTEM, "os2_write_to_device",
-                         "DosPostEventSem - rc=", rc);
-    }
-
-    /* advance to the next entry */
-    s->readyNdx = (s->readyNdx + cnt) % s->bufCnt;
-    s->readyCnt -= cnt;
-  }
-
-  /* if state is INIT or RECOVER, change to PLAY */
-  if (s->state < SAOS2_PLAY)
-    s->state = SAOS2_PLAY;
-
-  return SA_SUCCESS;
-}
-
-/*****************************************************************************/
-
 /** stop playback */
 
 static void os2_stop_device(uint16_t hwDeviceID)
 {
   uint32_t          rc;
   MCI_GENERIC_PARMS GenericParms = { 0 };
 
-  rc = mciSendCommand(hwDeviceID, MCI_STOP,
+  rc = _mciSendCommand(hwDeviceID, MCI_STOP,
                       MCI_WAIT,
                       (void*)&GenericParms, 0);
   if (LOUSHORT(rc))
     os2_error(0, "os2_stop_device", "MCI_STOP - rc=", LOUSHORT(rc));
 
   return;
 }
 
@@ -757,54 +768,56 @@ static void os2_stop_device(uint16_t hwD
 
 /** pause playback and optionally release device */
 
 static int  os2_pause_device(uint16_t hwDeviceID, uint32_t release)
 {
   uint32_t          rc;
   MCI_GENERIC_PARMS GenericParms = { 0 };
 
-  rc = mciSendCommand(hwDeviceID, MCI_PAUSE,
+  rc = _mciSendCommand(hwDeviceID, MCI_PAUSE,
                       MCI_WAIT,
                       (void*)&GenericParms, 0);
   if (LOUSHORT(rc))
     return os2_error(SA_ERROR_SYSTEM, "os2_pause_device",
                      "MCI_PAUSE - rc=", LOUSHORT(rc));
 
   if (release)
-    mciSendCommand(hwDeviceID, MCI_RELEASEDEVICE,
+    _mciSendCommand(hwDeviceID, MCI_RELEASEDEVICE,
                    MCI_WAIT,
                    (void*)&GenericParms, 0);
 
   return SA_SUCCESS;
 }
 
 /*****************************************************************************/
 
 /** update the count of free buffers, returning when 'count' are available */
 
 static int  os2_get_free_count(sa_stream_t *s, int32_t count)
 {
-  uint32_t      rc;
-  int32_t       posted;
+  uint32_t  timeout = 0;
 
   while (1) {
-    rc = DosResetEventSem(s->freeSem, (unsigned long*)&posted);
-    if (rc && rc != ERROR_ALREADY_RESET)
-      return os2_error(SA_ERROR_SYSTEM, "os2_get_free_count",
-                       "DosResetEventSem - rc=", rc);
+    uint32_t now;
 
-    s->freeCnt += posted;
+    s->freeCnt += __atomic_xchg(&s->freeNew, 0);
     if (s->freeCnt >= count)
       break;
 
-    rc = DosWaitEventSem(s->freeSem, SAOS2_SEM_WAIT);
-    if (rc)
+    /* get the current time in milliseconds */
+    DosQuerySysInfo(QSV_MS_COUNT, QSV_MS_COUNT, &now, sizeof(now));
+    if (!timeout)
+      timeout = now + SAOS2_WAIT;
+
+    if (now > timeout)
       return os2_error(SA_ERROR_SYSTEM, "os2_get_free_count",
-                       "DosWaitEventSem - rc=", rc);
+                       "timed-out waiting for free buffer(s)", 0);
+
+    DosSleep(1);
   }
 
   return SA_SUCCESS;
 }
 
 /*****************************************************************************/
 
 #ifdef SAOS2_ERROR
@@ -820,16 +833,47 @@ static int  os2_error_msg(int rtn, char 
   fflush(stderr);
 
   return rtn;
 }
 
 #endif
 
 /*****************************************************************************/
+/*  Mozilla-specific Function                                                */
+/*****************************************************************************/
+
+/** load mdm.dll & get the entrypoint for mciSendCommand() */
+
+static int  os2_load_mdm(void)
+{
+  uint32_t  rc;
+  HMODULE   hmod;
+  char      text[32];
+
+  if (_mciSendCommand)
+    return SA_SUCCESS;
+
+  rc = DosLoadModule(text, sizeof(text), "MDM", &hmod);
+  if (rc)
+    return os2_error(SA_ERROR_SYSTEM, "os2_load_mdm",
+                     "DosLoadModule - rc=", rc);
+
+  /* the ordinal for mciSendCommand is '1' */
+  rc = DosQueryProcAddr(hmod, 1, 0, (PFN*)&_mciSendCommand);
+  if (rc) {
+    _mciSendCommand = 0;
+    return os2_error(SA_ERROR_SYSTEM, "os2_load_mdm",
+                     "DosQueryProcAddr - rc=", rc);
+  }
+
+  return SA_SUCCESS;
+}
+
+/*****************************************************************************/
 /*  Not Implemented / Not Supported                                          */
 /*****************************************************************************/
 
 #define UNSUPPORTED(func)   func { return SA_ERROR_NOT_SUPPORTED; }
 
 UNSUPPORTED(int sa_stream_create_opaque(sa_stream_t **s, const char *client_name, sa_mode_t mode, const char *codec))
 UNSUPPORTED(int sa_stream_set_write_lower_watermark(sa_stream_t *s, size_t size))
 UNSUPPORTED(int sa_stream_set_read_lower_watermark(sa_stream_t *s, size_t size))