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 #include #include #include #include "sydney_audio.h" #define INCL_DOS #define INCL_MCIOS2 #include #include +#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))