mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
902 lines
31 KiB
Diff
902 lines
31 KiB
Diff
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))
|