mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
921 lines
33 KiB
Diff
921 lines
33 KiB
Diff
diff --git a/media/libsydneyaudio/include/sydney_audio.h b/media/libsydneyaudio/include/sydney_audio.h
|
|
--- a/media/libsydneyaudio/include/sydney_audio.h
|
|
+++ b/media/libsydneyaudio/include/sydney_audio.h
|
|
@@ -95,18 +95,18 @@ typedef __int32 int32_t;
|
|
#endif
|
|
#if !defined(int64_t)
|
|
typedef __int64 int64_t;
|
|
#endif
|
|
#endif
|
|
|
|
typedef struct sa_stream sa_stream_t;
|
|
|
|
-#if defined(WIN32)
|
|
-// (left << 16 | right) (16 bits per channel)
|
|
+#if defined(WIN32) || defined(OS2)
|
|
+/* (left << 16 | right) (16 bits per channel) */
|
|
#define SA_VOLUME_MUTED ((int32_t) (0x00000000))
|
|
#else
|
|
/** Volume that corresponds to muted in/out */
|
|
#define SA_VOLUME_MUTED ((int32_t) (-0x80000000))
|
|
#endif
|
|
|
|
/** Ways to express seek offsets for pread/pwrite */
|
|
typedef enum {
|
|
diff --git a/media/libsydneyaudio/src/sydney_audio_os2.c b/media/libsydneyaudio/src/sydney_audio_os2.c
|
|
new file mode 100644
|
|
--- /dev/null
|
|
+++ b/media/libsydneyaudio/src/sydney_audio_os2.c
|
|
@@ -0,0 +1,889 @@
|
|
+/* ***** BEGIN LICENSE BLOCK *****
|
|
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
|
+ *
|
|
+ * The contents of this file are subject to the Mozilla Public License Version
|
|
+ * 1.1 (the "License"); you may not use this file except in compliance with
|
|
+ * the License. You may obtain a copy of the License at
|
|
+ * http://www.mozilla.org/MPL/
|
|
+ *
|
|
+ * Software distributed under the License is distributed on an "AS IS" basis,
|
|
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
|
+ * for the specific language governing rights and limitations under the
|
|
+ * License.
|
|
+ *
|
|
+ * The Initial Developers of the Original Code are
|
|
+ * Andrew Zabolotny (libdart) - Copyright (C) 1998
|
|
+ * CSIRO (libsydneyaudio)- Copyright (C) 2007
|
|
+ * Richard Walsh (OS/2 implementation) - Copyright (C) 2008
|
|
+ * Portions created by the Initial Developers are Copyright (c) 1998,2007,2008,
|
|
+ * the Initial Developers. All Rights Reserved.
|
|
+ *
|
|
+ * Contributor(s):
|
|
+ *
|
|
+ * Alternatively, the contents of this file may be used under the terms of
|
|
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
|
|
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
|
+ * in which case the provisions of the GPL or the LGPL are applicable instead
|
|
+ * of those above. If you wish to allow use of your version of this file only
|
|
+ * under the terms of either the GPL or the LGPL, and not to allow others to
|
|
+ * use your version of this file under the terms of the MPL, indicate your
|
|
+ * decision by deleting the provisions above and replace them with the notice
|
|
+ * and other provisions required by the GPL or the LGPL. If you do not delete
|
|
+ * the provisions above, a recipient may use your version of this file under
|
|
+ * the terms of any one of the MPL, the GPL or the LGPL.
|
|
+ *
|
|
+ * ***** END LICENSE BLOCK ***** *
|
|
+ */
|
|
+
|
|
+/*****************************************************************************/
|
|
+/* OVERVIEW
|
|
+ *
|
|
+ * Unlike other DART implementations which pull data into the backend
|
|
+ * as needed, this one relies on the upstream code to provide sufficient
|
|
+ * data in a well regulated stream. If other activities in the system
|
|
+ * 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.
|
|
+ *
|
|
+ */
|
|
+/*****************************************************************************/
|
|
+
|
|
+#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>
|
|
+
|
|
+/*****************************************************************************/
|
|
+
|
|
+/* 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
|
|
+
|
|
+/* playback states */
|
|
+#define SAOS2_INIT 0
|
|
+#define SAOS2_RECOVER 1
|
|
+#define SAOS2_PLAY 2
|
|
+#define SAOS2_EXIT 3
|
|
+
|
|
+/* an indefinite wait invites a hung thread */
|
|
+#define SAOS2_SEM_WAIT 5000
|
|
+
|
|
+/* the only 2 return codes we care about */
|
|
+#ifndef INCL_DOSERRORS
|
|
+#define ERROR_ALREADY_POSTED 299
|
|
+#define ERROR_ALREADY_RESET 300
|
|
+#endif
|
|
+
|
|
+/*****************************************************************************/
|
|
+/* Debug */
|
|
+
|
|
+#ifdef DEBUG
|
|
+ #ifndef SAOS2_ERROR
|
|
+ #define SAOS2_ERROR
|
|
+ #endif
|
|
+#endif
|
|
+
|
|
+#ifdef SAOS2_ERROR
|
|
+ static int os2_error_msg(int rtn, char * func, char * msg, uint32_t err);
|
|
+ #define os2_error(rtn, func, msg, err) os2_error_msg(rtn, func, msg, err)
|
|
+#else
|
|
+ #define os2_error(rtn, func, msg, err) rtn
|
|
+#endif
|
|
+
|
|
+/*****************************************************************************/
|
|
+/* OS/2 implementation of sa_stream_t */
|
|
+
|
|
+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;
|
|
+
|
|
+ /* device info */
|
|
+ uint16_t hwDeviceID;
|
|
+ uint32_t hwMixHandle;
|
|
+ PMIXERPROC hwWriteProc;
|
|
+
|
|
+ /* buffer allocations */
|
|
+ int32_t bufCnt;
|
|
+ size_t bufSize;
|
|
+ PMCI_MIX_BUFFER bufList;
|
|
+
|
|
+ /* buffer usage tracking */
|
|
+ HEV freeSem;
|
|
+ int32_t freeCnt;
|
|
+ int32_t freeNdx;
|
|
+ int32_t readyCnt;
|
|
+ int32_t readyNdx;
|
|
+ HEV usedSem;
|
|
+ volatile int32_t usedCnt;
|
|
+
|
|
+ /* miscellaneous */
|
|
+ volatile int32_t state;
|
|
+ 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);
|
|
+
|
|
+/*****************************************************************************/
|
|
+/* 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,
|
|
+ sa_pcm_format_t format,
|
|
+ unsigned int rate,
|
|
+ unsigned int nchannels)
|
|
+{
|
|
+ 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 {
|
|
+ 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;
|
|
+
|
|
+ /* the MCI_MIX_BUFFERs must be in low memory or terrible things will
|
|
+ * happen! - since there's extra space, put 'sa_stream' there too */
|
|
+ size = sizeof(sa_stream_t) + sizeof(PMCI_MIX_BUFFER) * SAOS2_BUF_CNT;
|
|
+ rc = DosAllocMem((void**)&sTemp, size,
|
|
+ PAG_COMMIT | PAG_READ | PAG_WRITE);
|
|
+ if (rc) {
|
|
+ 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;
|
|
+
|
|
+ *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;
|
|
+}
|
|
+
|
|
+/*****************************************************************************/
|
|
+
|
|
+/** Initialise the device */
|
|
+
|
|
+int sa_stream_open(sa_stream_t *s)
|
|
+{
|
|
+ int status = SA_SUCCESS;
|
|
+ uint32_t rc;
|
|
+ int32_t ctr;
|
|
+ uint32_t bufCntRequested;
|
|
+ MCI_AMP_OPEN_PARMS AmpOpenParms;
|
|
+ MCI_MIXSETUP_PARMS MixSetupParms;
|
|
+ MCI_BUFFER_PARMS BufferParms;
|
|
+
|
|
+ if (!s)
|
|
+ return os2_error(SA_ERROR_NO_INIT, "sa_stream_open", "s is null", 0);
|
|
+
|
|
+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,
|
|
+ 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;
|
|
+ }
|
|
+
|
|
+ /* save the device ID */
|
|
+ s->hwDeviceID = AmpOpenParms.usDeviceID;
|
|
+
|
|
+ /* setup the Amp-Mixer to play wave data */
|
|
+ memset(&MixSetupParms, 0, sizeof(MCI_MIXSETUP_PARMS));
|
|
+ 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,
|
|
+ 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;
|
|
+ }
|
|
+
|
|
+ /* save hw info we'll need later */
|
|
+ s->hwMixHandle = MixSetupParms.ulMixHandle;
|
|
+ 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,
|
|
+ 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 */
|
|
+ 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);
|
|
+
|
|
+ return status;
|
|
+}
|
|
+
|
|
+/*****************************************************************************/
|
|
+
|
|
+/** Close/destroy everything */
|
|
+
|
|
+int sa_stream_destroy(sa_stream_t *s)
|
|
+{
|
|
+ int status = SA_SUCCESS;
|
|
+ uint32_t rc;
|
|
+ MCI_GENERIC_PARMS GenericParms = { 0 };
|
|
+ MCI_BUFFER_PARMS BufferParms;
|
|
+
|
|
+ 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;
|
|
+
|
|
+ /* 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,
|
|
+ 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,
|
|
+ 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 */
|
|
+
|
|
+int sa_stream_write(sa_stream_t * s, const void * data, size_t nbytes)
|
|
+{
|
|
+ uint32_t rc;
|
|
+ size_t cnt;
|
|
+ PMCI_MIX_BUFFER pHW;
|
|
+
|
|
+ if (!s)
|
|
+ return os2_error(SA_ERROR_NO_INIT, "sa_stream_write", "s is null", 0);
|
|
+ if (!data)
|
|
+ return os2_error(SA_ERROR_INVALID, "sa_stream_write", "data is null", 0);
|
|
+
|
|
+ /* 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) {
|
|
+
|
|
+ /* 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;
|
|
+ nbytes -= cnt;
|
|
+ data = (char*)data + cnt;
|
|
+
|
|
+ /* adjust cnts & indices, then send the buffer to the device */
|
|
+ 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;
|
|
+
|
|
+ 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 SA_SUCCESS;
|
|
+}
|
|
+
|
|
+/*****************************************************************************/
|
|
+
|
|
+/** Resume playing after a pause */
|
|
+
|
|
+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,
|
|
+ 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,
|
|
+ MCI_WAIT,
|
|
+ (void*)&GenericParms, 0);
|
|
+ if (LOUSHORT(rc))
|
|
+ return os2_error(SA_ERROR_SYSTEM, "sa_stream_resume",
|
|
+ "MCI_RESUME - rc=", LOUSHORT(rc));
|
|
+
|
|
+ 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 */
|
|
+ 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;
|
|
+
|
|
+ /* 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));
|
|
+ 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);
|
|
+
|
|
+ /* stop the device so it doesn't misbehave due to an under-run */
|
|
+ os2_stop_device(s->hwDeviceID);
|
|
+
|
|
+ return status;
|
|
+}
|
|
+
|
|
+/*****************************************************************************/
|
|
+
|
|
+/** Query how much can be written without blocking */
|
|
+
|
|
+int sa_stream_get_write_size(sa_stream_t *s, size_t *size)
|
|
+{
|
|
+ if (!s)
|
|
+ return os2_error(SA_ERROR_NO_INIT, "sa_stream_get_write_size",
|
|
+ "s is null", 0);
|
|
+
|
|
+ /* 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;
|
|
+
|
|
+ return SA_SUCCESS;
|
|
+}
|
|
+
|
|
+/*****************************************************************************/
|
|
+
|
|
+/** set absolute volume using a value ranging from 0.0 to 1.0 */
|
|
+
|
|
+int sa_stream_set_volume_abs(sa_stream_t *s, float vol)
|
|
+{
|
|
+ uint32_t rc;
|
|
+ MCI_SET_PARMS SetParms;
|
|
+
|
|
+ if (!s)
|
|
+ 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,
|
|
+ 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;
|
|
+}
|
|
+
|
|
+/*****************************************************************************/
|
|
+
|
|
+/** get absolute volume as a value ranging from 0.0 to 1.0 */
|
|
+
|
|
+int sa_stream_get_volume_abs(sa_stream_t *s, float *vol)
|
|
+{
|
|
+ int status = SA_SUCCESS;
|
|
+ uint32_t rc;
|
|
+ MCI_STATUS_PARMS StatusParms;
|
|
+
|
|
+ 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,
|
|
+ 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));
|
|
+ }
|
|
+
|
|
+ /* left channel is the low-order word, right channel is the
|
|
+ * high-order word - convert the average of the channels from
|
|
+ * an integer (range 0 - 100) to a floating point value */
|
|
+
|
|
+ *vol = (LOUSHORT(StatusParms.ulReturn) +
|
|
+ HIUSHORT(StatusParms.ulReturn)) / 200.0;
|
|
+
|
|
+ return status;
|
|
+}
|
|
+
|
|
+/*****************************************************************************/
|
|
+/* Private (static) Functions */
|
|
+/*****************************************************************************/
|
|
+
|
|
+/** 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);
|
|
+
|
|
+ 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;
|
|
+
|
|
+ /* 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;
|
|
+ os2_pause_device(s->hwDeviceID, FALSE);
|
|
+ rc = os2_error(rc, "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;
|
|
+ pBuffer->ulBufferLength = 0;
|
|
+
|
|
+ /* 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);
|
|
+
|
|
+ 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,
|
|
+ MCI_WAIT,
|
|
+ (void*)&GenericParms, 0);
|
|
+ if (LOUSHORT(rc))
|
|
+ os2_error(0, "os2_stop_device", "MCI_STOP - rc=", LOUSHORT(rc));
|
|
+
|
|
+ return;
|
|
+}
|
|
+
|
|
+/*****************************************************************************/
|
|
+
|
|
+/** 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,
|
|
+ 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,
|
|
+ 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;
|
|
+
|
|
+ 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);
|
|
+
|
|
+ s->freeCnt += posted;
|
|
+ if (s->freeCnt >= count)
|
|
+ break;
|
|
+
|
|
+ rc = DosWaitEventSem(s->freeSem, SAOS2_SEM_WAIT);
|
|
+ if (rc)
|
|
+ return os2_error(SA_ERROR_SYSTEM, "os2_get_free_count",
|
|
+ "DosWaitEventSem - rc=", rc);
|
|
+ }
|
|
+
|
|
+ return SA_SUCCESS;
|
|
+}
|
|
+
|
|
+/*****************************************************************************/
|
|
+
|
|
+#ifdef SAOS2_ERROR
|
|
+
|
|
+/** display an error message & return whatever value was passed in */
|
|
+
|
|
+static int os2_error_msg(int rtn, char * func, char * msg, uint32_t err)
|
|
+{
|
|
+ if (!err)
|
|
+ fprintf(stderr, "sa_os2 error - %s: %s\n", func, msg);
|
|
+ else
|
|
+ fprintf(stderr, "sa_os2 error - %s: %s %u\n", func, msg, err);
|
|
+ fflush(stderr);
|
|
+
|
|
+ return rtn;
|
|
+}
|
|
+
|
|
+#endif
|
|
+
|
|
+/*****************************************************************************/
|
|
+/* 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))
|
|
+UNSUPPORTED(int sa_stream_set_write_upper_watermark(sa_stream_t *s, size_t size))
|
|
+UNSUPPORTED(int sa_stream_set_read_upper_watermark(sa_stream_t *s, size_t size))
|
|
+UNSUPPORTED(int sa_stream_set_channel_map(sa_stream_t *s, const sa_channel_t map[], unsigned int n))
|
|
+UNSUPPORTED(int sa_stream_set_xrun_mode(sa_stream_t *s, sa_xrun_mode_t mode))
|
|
+UNSUPPORTED(int sa_stream_set_non_interleaved(sa_stream_t *s, int enable))
|
|
+UNSUPPORTED(int sa_stream_set_dynamic_rate(sa_stream_t *s, int enable))
|
|
+UNSUPPORTED(int sa_stream_set_driver(sa_stream_t *s, const char *driver))
|
|
+UNSUPPORTED(int sa_stream_start_thread(sa_stream_t *s, sa_event_callback_t callback))
|
|
+UNSUPPORTED(int sa_stream_stop_thread(sa_stream_t *s))
|
|
+UNSUPPORTED(int sa_stream_change_device(sa_stream_t *s, const char *device_name))
|
|
+UNSUPPORTED(int sa_stream_change_read_volume(sa_stream_t *s, const int32_t vol[], unsigned int n))
|
|
+UNSUPPORTED(int sa_stream_change_write_volume(sa_stream_t *s, const int32_t vol[], unsigned int n))
|
|
+UNSUPPORTED(int sa_stream_change_rate(sa_stream_t *s, unsigned int rate))
|
|
+UNSUPPORTED(int sa_stream_change_meta_data(sa_stream_t *s, const char *name, const void *data, size_t size))
|
|
+UNSUPPORTED(int sa_stream_change_user_data(sa_stream_t *s, const void *value))
|
|
+UNSUPPORTED(int sa_stream_set_adjust_rate(sa_stream_t *s, sa_adjust_t direction))
|
|
+UNSUPPORTED(int sa_stream_set_adjust_nchannels(sa_stream_t *s, sa_adjust_t direction))
|
|
+UNSUPPORTED(int sa_stream_set_adjust_pcm_format(sa_stream_t *s, sa_adjust_t direction))
|
|
+UNSUPPORTED(int sa_stream_set_adjust_watermarks(sa_stream_t *s, sa_adjust_t direction))
|
|
+UNSUPPORTED(int sa_stream_get_mode(sa_stream_t *s, sa_mode_t *access_mode))
|
|
+UNSUPPORTED(int sa_stream_get_codec(sa_stream_t *s, char *codec, size_t *size))
|
|
+UNSUPPORTED(int sa_stream_get_pcm_format(sa_stream_t *s, sa_pcm_format_t *format))
|
|
+UNSUPPORTED(int sa_stream_get_rate(sa_stream_t *s, unsigned int *rate))
|
|
+UNSUPPORTED(int sa_stream_get_nchannels(sa_stream_t *s, int *nchannels))
|
|
+UNSUPPORTED(int sa_stream_get_user_data(sa_stream_t *s, void **value))
|
|
+UNSUPPORTED(int sa_stream_get_write_lower_watermark(sa_stream_t *s, size_t *size))
|
|
+UNSUPPORTED(int sa_stream_get_read_lower_watermark(sa_stream_t *s, size_t *size))
|
|
+UNSUPPORTED(int sa_stream_get_write_upper_watermark(sa_stream_t *s, size_t *size))
|
|
+UNSUPPORTED(int sa_stream_get_read_upper_watermark(sa_stream_t *s, size_t *size))
|
|
+UNSUPPORTED(int sa_stream_get_channel_map(sa_stream_t *s, sa_channel_t map[], unsigned int *n))
|
|
+UNSUPPORTED(int sa_stream_get_xrun_mode(sa_stream_t *s, sa_xrun_mode_t *mode))
|
|
+UNSUPPORTED(int sa_stream_get_non_interleaved(sa_stream_t *s, int *enabled))
|
|
+UNSUPPORTED(int sa_stream_get_dynamic_rate(sa_stream_t *s, int *enabled))
|
|
+UNSUPPORTED(int sa_stream_get_driver(sa_stream_t *s, char *driver_name, size_t *size))
|
|
+UNSUPPORTED(int sa_stream_get_device(sa_stream_t *s, char *device_name, size_t *size))
|
|
+UNSUPPORTED(int sa_stream_get_read_volume(sa_stream_t *s, int32_t vol[], unsigned int *n))
|
|
+UNSUPPORTED(int sa_stream_get_write_volume(sa_stream_t *s, int32_t vol[], unsigned int *n))
|
|
+UNSUPPORTED(int sa_stream_get_meta_data(sa_stream_t *s, const char *name, void*data, size_t *size))
|
|
+UNSUPPORTED(int sa_stream_get_adjust_rate(sa_stream_t *s, sa_adjust_t *direction))
|
|
+UNSUPPORTED(int sa_stream_get_adjust_nchannels(sa_stream_t *s, sa_adjust_t *direction))
|
|
+UNSUPPORTED(int sa_stream_get_adjust_pcm_format(sa_stream_t *s, sa_adjust_t *direction))
|
|
+UNSUPPORTED(int sa_stream_get_adjust_watermarks(sa_stream_t *s, sa_adjust_t *direction))
|
|
+UNSUPPORTED(int sa_stream_get_state(sa_stream_t *s, sa_state_t *state))
|
|
+UNSUPPORTED(int sa_stream_get_event_error(sa_stream_t *s, sa_error_t *error))
|
|
+UNSUPPORTED(int sa_stream_get_event_notify(sa_stream_t *s, sa_notify_t *notify))
|
|
+UNSUPPORTED(int sa_stream_read(sa_stream_t *s, void *data, size_t nbytes))
|
|
+UNSUPPORTED(int sa_stream_read_ni(sa_stream_t *s, unsigned int channel, void *data, size_t nbytes))
|
|
+UNSUPPORTED(int sa_stream_write_ni(sa_stream_t *s, unsigned int channel, const void *data, size_t nbytes))
|
|
+UNSUPPORTED(int sa_stream_pwrite(sa_stream_t *s, const void *data, size_t nbytes, int64_t offset, sa_seek_t whence))
|
|
+UNSUPPORTED(int sa_stream_pwrite_ni(sa_stream_t *s, unsigned int channel, const void *data, size_t nbytes, int64_t offset, sa_seek_t whence))
|
|
+UNSUPPORTED(int sa_stream_get_read_size(sa_stream_t *s, size_t *size))
|
|
+
|
|
+const char *sa_strerror(int code) { return NULL; }
|
|
+
|
|
+/*****************************************************************************/
|
|
+
|