| /* |
| Simple DirectMedia Layer |
| Copyright (C) 1997-2020 Sam Lantinga <slouken@libsdl.org> |
| |
| This software is provided 'as-is', without any express or implied |
| warranty. In no event will the authors be held liable for any damages |
| arising from the use of this software. |
| |
| Permission is granted to anyone to use this software for any purpose, |
| including commercial applications, and to alter it and redistribute it |
| freely, subject to the following restrictions: |
| |
| 1. The origin of this software must not be misrepresented; you must not |
| claim that you wrote the original software. If you use this software |
| in a product, an acknowledgment in the product documentation would be |
| appreciated but is not required. |
| 2. Altered source versions must be plainly marked as such, and must not be |
| misrepresented as being the original software. |
| 3. This notice may not be removed or altered from any source distribution. |
| */ |
| #include "../../SDL_internal.h" |
| |
| #if SDL_AUDIO_DRIVER_OS2 |
| |
| /* Allow access to a raw mixing buffer */ |
| |
| #include "../../core/os2/SDL_os2.h" |
| |
| #include "SDL_timer.h" |
| #include "SDL_audio.h" |
| #include "../SDL_audio_c.h" |
| #include "SDL_os2audio.h" |
| |
| /* |
| void lockIncr(volatile int *piVal); |
| #pragma aux lockIncr = \ |
| " lock add [eax], 1 "\ |
| parm [eax]; |
| |
| void lockDecr(volatile int *piVal); |
| #pragma aux lockDecr = \ |
| " lock sub [eax], 1 "\ |
| parm [eax]; |
| */ |
| |
| |
| static ULONG _getEnvULong(PSZ pszName, ULONG ulMax, ULONG ulDefault) |
| { |
| ULONG ulValue; |
| PCHAR pcEnd; |
| PSZ pszEnvVal = SDL_getenv( pszName ); |
| |
| if ( pszEnvVal == NULL ) |
| return ulDefault; |
| |
| ulValue = SDL_strtoul( (const char *)pszEnvVal, &pcEnd, 10 ); |
| return ( pcEnd == pszEnvVal ) || ( ulValue > ulMax ) ? ulDefault : ulMax; |
| } |
| |
| static int _MCIError(PSZ pszFunc, ULONG ulResult) |
| { |
| CHAR acBuf[128]; |
| |
| mciGetErrorString( ulResult, acBuf, sizeof(acBuf) ); |
| return SDL_SetError( "[%s] %s", pszFunc, acBuf ); |
| } |
| |
| static void _mixIOError(PSZ pszFunction, ULONG ulRC) |
| { |
| debug( "%s() - failed, rc = 0x%X (%s)", |
| pszFunction, ulRC, |
| ulRC == MCIERR_INVALID_MODE ? "Mixer mode does not match request" |
| : ulRC == MCIERR_INVALID_BUFFER ? "Caller sent an invalid buffer" |
| : "unknown" ); |
| } |
| |
| LONG APIENTRY cbAudioWriteEvent(ULONG ulStatus, PMCI_MIX_BUFFER pBuffer, |
| ULONG ulFlags) |
| { |
| PSDL_PrivateAudioData pAData = (PSDL_PrivateAudioData)pBuffer->ulUserParm; |
| ULONG ulRC; |
| |
| if ( ulFlags != MIX_WRITE_COMPLETE ) |
| { |
| debug( "flags = 0x%X", ulFlags ); |
| return 0; |
| } |
| |
| // lockDecr( (int *)&pAData->ulQueuedBuf ); |
| ulRC = DosPostEventSem( pAData->hevBuf ); |
| if ( ( ulRC != NO_ERROR ) && ( ulRC != ERROR_ALREADY_POSTED ) ) |
| debug( "DosPostEventSem(), rc = %u", ulRC ); |
| |
| return 1; // It seems, return value is not matter. |
| } |
| |
| LONG APIENTRY cbAudioReadEvent(ULONG ulStatus, PMCI_MIX_BUFFER pBuffer, |
| ULONG ulFlags) |
| { |
| PSDL_PrivateAudioData pAData = (PSDL_PrivateAudioData)pBuffer->ulUserParm; |
| ULONG ulRC; |
| |
| if ( ulFlags != MIX_READ_COMPLETE ) |
| { |
| debug( "flags = 0x%X", ulFlags ); |
| return 0; |
| } |
| |
| pAData->stMCIMixSetup.pmixRead( pAData->stMCIMixSetup.ulMixHandle, pBuffer, |
| 1 ); |
| |
| ulRC = DosPostEventSem( pAData->hevBuf ); |
| if ( ( ulRC != NO_ERROR ) && ( ulRC != ERROR_ALREADY_POSTED ) ) |
| debug( "DosPostEventSem(), rc = %u", ulRC ); |
| |
| return 1; |
| } |
| |
| |
| static void OS2_DetectDevices(void) |
| { |
| MCI_SYSINFO_PARMS stMCISysInfo; |
| CHAR acBuf[256]; |
| ULONG ulDevicesNum; |
| MCI_SYSINFO_LOGDEVICE stLogDevice; |
| MCI_SYSINFO_PARMS stSysInfoParams; |
| ULONG ulRC; |
| ULONG ulHandle = 0; |
| |
| acBuf[0] = '\0'; |
| stMCISysInfo.pszReturn = acBuf; |
| stMCISysInfo.ulRetSize = sizeof(acBuf); |
| stMCISysInfo.usDeviceType = MCI_DEVTYPE_AUDIO_AMPMIX; |
| ulRC = mciSendCommand( 0, MCI_SYSINFO, MCI_WAIT | MCI_SYSINFO_QUANTITY, |
| &stMCISysInfo, 0 ); |
| if ( ulRC != NO_ERROR ) |
| { |
| debug( "MCI_SYSINFO, MCI_SYSINFO_QUANTITY - failed, rc = 0x%X", ulRC ); |
| return; |
| } |
| |
| ulDevicesNum = atol( stMCISysInfo.pszReturn ); |
| |
| for( stSysInfoParams.ulNumber = 0; stSysInfoParams.ulNumber < ulDevicesNum; |
| stSysInfoParams.ulNumber++ ) |
| { |
| // Get device install name. |
| stSysInfoParams.pszReturn = acBuf; |
| stSysInfoParams.ulRetSize = sizeof( acBuf ); |
| stSysInfoParams.usDeviceType = MCI_DEVTYPE_AUDIO_AMPMIX; |
| ulRC = mciSendCommand( 0, MCI_SYSINFO, MCI_WAIT | MCI_SYSINFO_INSTALLNAME, |
| &stSysInfoParams, 0 ); |
| if ( ulRC != NO_ERROR ) |
| { |
| debug( "MCI_SYSINFO, MCI_SYSINFO_INSTALLNAME - failed, rc = 0x%X", ulRC ); |
| continue; |
| } |
| |
| // Get textual product description. |
| stSysInfoParams.ulItem = MCI_SYSINFO_QUERY_DRIVER; |
| stSysInfoParams.pSysInfoParm = &stLogDevice; |
| strcpy( stLogDevice.szInstallName, stSysInfoParams.pszReturn ); |
| ulRC = mciSendCommand( 0, MCI_SYSINFO, MCI_WAIT | MCI_SYSINFO_ITEM, |
| &stSysInfoParams, 0 ); |
| if ( ulRC != NO_ERROR ) |
| { |
| debug( "MCI_SYSINFO, MCI_SYSINFO_ITEM - failed, rc = 0x%X", ulRC ); |
| continue; |
| } |
| |
| ulHandle++; |
| SDL_AddAudioDevice( 0, stLogDevice.szProductInfo, (void *)(ulHandle) ); |
| ulHandle++; |
| SDL_AddAudioDevice( 1, stLogDevice.szProductInfo, (void *)(ulHandle) ); |
| } |
| } |
| |
| static void OS2_WaitDevice(_THIS) |
| { |
| PSDL_PrivateAudioData pAData = (PSDL_PrivateAudioData)this->hidden; |
| ULONG ulRC; |
| |
| /* Wait for an audio chunk to finish */ |
| ulRC = DosWaitEventSem( pAData->hevBuf, 5000 ); |
| if ( ulRC != NO_ERROR ) |
| debug( "DosWaitEventSem(), rc = %u", ulRC ); |
| } |
| |
| static Uint8 *OS2_GetDeviceBuf(_THIS) |
| { |
| PSDL_PrivateAudioData pAData = (PSDL_PrivateAudioData)this->hidden; |
| |
| return pAData->aMixBuffers[pAData->ulNextBuf].pBuffer; |
| } |
| |
| static void OS2_PlayDevice(_THIS) |
| { |
| PSDL_PrivateAudioData pAData = (PSDL_PrivateAudioData)this->hidden; |
| ULONG ulRC; |
| PMCI_MIX_BUFFER pMixBuffer = &pAData->aMixBuffers[pAData->ulNextBuf]; |
| |
| /* Queue it up */ |
| // lockIncr( (int *)&pAData->ulQueuedBuf ); |
| ulRC = pAData->stMCIMixSetup.pmixWrite( pAData->stMCIMixSetup.ulMixHandle, |
| pMixBuffer, 1 ); |
| if ( ulRC != MCIERR_SUCCESS ) |
| _mixIOError( "pmixWrite", ulRC ); |
| else |
| pAData->ulNextBuf = (pAData->ulNextBuf + 1) % pAData->cMixBuffers; |
| } |
| |
| static void OS2_CloseDevice(_THIS) |
| { |
| PSDL_PrivateAudioData pAData = (PSDL_PrivateAudioData)this->hidden; |
| MCI_GENERIC_PARMS sMCIGenericParms; |
| ULONG ulRC; |
| |
| if ( pAData == NULL ) |
| return; |
| |
| /* Close up audio */ |
| if ( pAData->usDeviceId != (USHORT)~0 ) |
| { |
| // Device is open. |
| if ( pAData->stMCIMixSetup.ulBitsPerSample != 0 ) |
| { |
| // Mixer was initialized. |
| ulRC = mciSendCommand( pAData->usDeviceId, MCI_MIXSETUP, |
| MCI_WAIT | MCI_MIXSETUP_DEINIT, |
| &pAData->stMCIMixSetup, 0 ); |
| if ( ulRC != MCIERR_SUCCESS ) |
| debug( "MCI_MIXSETUP, MCI_MIXSETUP_DEINIT - failed" ); |
| } |
| |
| if ( pAData->cMixBuffers != 0 ) |
| { |
| // Buffers was allocated. |
| MCI_BUFFER_PARMS stMCIBuffer; |
| |
| stMCIBuffer.ulBufferSize = pAData->aMixBuffers[0].ulBufferLength; |
| stMCIBuffer.ulNumBuffers = pAData->cMixBuffers; |
| stMCIBuffer.pBufList = &pAData->aMixBuffers; |
| |
| ulRC = mciSendCommand( pAData->usDeviceId, MCI_BUFFER, |
| MCI_WAIT | MCI_DEALLOCATE_MEMORY, &stMCIBuffer, 0 ); |
| if ( ulRC != MCIERR_SUCCESS ) |
| debug( "MCI_BUFFER, MCI_DEALLOCATE_MEMORY - failed" ); |
| } |
| |
| ulRC = mciSendCommand( pAData->usDeviceId, MCI_CLOSE, MCI_WAIT, |
| &sMCIGenericParms, 0 ); |
| if ( ulRC != MCIERR_SUCCESS ) |
| debug( "MCI_CLOSE - failed" ); |
| } |
| |
| if ( pAData->hevBuf != NULLHANDLE ) |
| DosCloseEventSem( pAData->hevBuf ); |
| |
| SDL_free( pAData ); |
| } |
| |
| static int OS2_OpenDevice(_THIS, void *handle, const char *devname, |
| int iscapture) |
| { |
| PSDL_PrivateAudioData pAData; |
| SDL_AudioFormat SDLAudioFmt; |
| MCI_AMP_OPEN_PARMS stMCIAmpOpen; |
| MCI_BUFFER_PARMS stMCIBuffer; |
| ULONG ulRC; |
| ULONG ulIdx; |
| BOOL new_freq; |
| |
| new_freq = FALSE; |
| SDL_zero(stMCIAmpOpen); |
| SDL_zero(stMCIBuffer); |
| |
| for( SDLAudioFmt = SDL_FirstAudioFormat( this->spec.format ); |
| SDLAudioFmt != 0; SDLAudioFmt = SDL_NextAudioFormat() ) |
| { |
| if ( ( SDLAudioFmt == AUDIO_U8 ) || ( SDLAudioFmt == AUDIO_S16 ) ) |
| break; |
| } |
| |
| if ( SDLAudioFmt == 0 ) |
| { |
| debug( "Unsupported audio format, AUDIO_S16 used" ); |
| SDLAudioFmt = AUDIO_S16; |
| } |
| |
| pAData = SDL_calloc( 1, sizeof(SDL_PrivateAudioData) ); |
| if ( pAData == NULL ) |
| return SDL_OutOfMemory(); |
| this->hidden = pAData; |
| |
| ulRC = DosCreateEventSem( NULL, &pAData->hevBuf, DCE_AUTORESET, TRUE ); |
| if ( ulRC != NO_ERROR ) |
| { |
| debug( "DosCreateEventSem() failed, rc = %u", ulRC ); |
| return -1; |
| } |
| |
| // Open audio device |
| stMCIAmpOpen.usDeviceID = handle != NULL ? ( ((ULONG)handle) - 1 ) : 0; |
| stMCIAmpOpen.pszDeviceType = (PSZ)MCI_DEVTYPE_AUDIO_AMPMIX; |
| ulRC = mciSendCommand( 0, MCI_OPEN, |
| _getEnvULong( "SDL_AUDIO_SHARE", 1, 0 ) != 0 |
| ? MCI_WAIT | MCI_OPEN_TYPE_ID | MCI_OPEN_SHAREABLE |
| : MCI_WAIT | MCI_OPEN_TYPE_ID, |
| &stMCIAmpOpen, 0 ); |
| if ( ulRC != MCIERR_SUCCESS ) |
| { |
| stMCIAmpOpen.usDeviceID = (USHORT)~0; |
| return _MCIError( "MCI_OPEN", ulRC ); |
| } |
| pAData->usDeviceId = stMCIAmpOpen.usDeviceID; |
| |
| if ( iscapture != 0 ) |
| { |
| MCI_CONNECTOR_PARMS stMCIConnector; |
| MCI_AMP_SET_PARMS stMCIAmpSet; |
| BOOL fLineIn = _getEnvULong( "SDL_AUDIO_LINEIN", 1, 0 ); |
| |
| // Set particular connector. |
| SDL_zero(stMCIConnector); |
| stMCIConnector.ulConnectorType = fLineIn ? MCI_LINE_IN_CONNECTOR |
| : MCI_MICROPHONE_CONNECTOR; |
| mciSendCommand( stMCIAmpOpen.usDeviceID, MCI_CONNECTOR, |
| MCI_WAIT | MCI_ENABLE_CONNECTOR | |
| MCI_CONNECTOR_TYPE, &stMCIConnector, 0 ); |
| |
| // Disable monitor. |
| SDL_zero(stMCIAmpSet); |
| stMCIAmpSet.ulItem = MCI_AMP_SET_MONITOR; |
| mciSendCommand( stMCIAmpOpen.usDeviceID, MCI_SET, |
| MCI_WAIT | MCI_SET_OFF | MCI_SET_ITEM, |
| &stMCIAmpSet, 0 ); |
| |
| // Set record volume. |
| stMCIAmpSet.ulLevel = _getEnvULong( "SDL_AUDIO_RECVOL", 100, 90 ); |
| stMCIAmpSet.ulItem = MCI_AMP_SET_AUDIO; |
| stMCIAmpSet.ulAudio = MCI_SET_AUDIO_ALL; // Both cnannels. |
| stMCIAmpSet.ulValue = fLineIn ? MCI_LINE_IN_CONNECTOR |
| : MCI_MICROPHONE_CONNECTOR ; |
| |
| mciSendCommand( stMCIAmpOpen.usDeviceID, MCI_SET, |
| MCI_WAIT | MCI_SET_AUDIO | MCI_AMP_SET_GAIN, |
| &stMCIAmpSet, 0 ); |
| } |
| |
| this->spec.format = SDLAudioFmt; |
| this->spec.channels = this->spec.channels > 1 ? 2 : 1; |
| if ( this->spec.freq < 8000 ) |
| { |
| this->spec.freq = 8000; |
| new_freq = TRUE; |
| } |
| else if ( this->spec.freq > 48000 ) |
| { |
| this->spec.freq = 48000; |
| new_freq = TRUE; |
| } |
| |
| // Setup mixer. |
| pAData->stMCIMixSetup.ulFormatTag = MCI_WAVE_FORMAT_PCM; |
| pAData->stMCIMixSetup.ulBitsPerSample = SDL_AUDIO_BITSIZE( SDLAudioFmt ); |
| pAData->stMCIMixSetup.ulSamplesPerSec = this->spec.freq; |
| pAData->stMCIMixSetup.ulChannels = this->spec.channels; |
| pAData->stMCIMixSetup.ulDeviceType = MCI_DEVTYPE_WAVEFORM_AUDIO; |
| if ( iscapture == 0 ) |
| { |
| pAData->stMCIMixSetup.ulFormatMode = MCI_PLAY; |
| pAData->stMCIMixSetup.pmixEvent = cbAudioWriteEvent; |
| } |
| else |
| { |
| pAData->stMCIMixSetup.ulFormatMode = MCI_RECORD; |
| pAData->stMCIMixSetup.pmixEvent = cbAudioReadEvent; |
| } |
| |
| ulRC = mciSendCommand( pAData->usDeviceId, MCI_MIXSETUP, |
| MCI_WAIT | MCI_MIXSETUP_INIT, &pAData->stMCIMixSetup, 0 ); |
| if ( ( ulRC != MCIERR_SUCCESS ) && ( this->spec.freq > 44100 ) ) |
| { |
| new_freq = TRUE; |
| pAData->stMCIMixSetup.ulSamplesPerSec = 44100; |
| this->spec.freq = 44100; |
| ulRC = mciSendCommand( pAData->usDeviceId, MCI_MIXSETUP, |
| MCI_WAIT | MCI_MIXSETUP_INIT, &pAData->stMCIMixSetup, 0 ); |
| } |
| |
| debug( "Setup mixer [BPS: %u, Freq.: %u, Channels: %u]: %s", |
| pAData->stMCIMixSetup.ulBitsPerSample, |
| pAData->stMCIMixSetup.ulSamplesPerSec, |
| pAData->stMCIMixSetup.ulChannels, |
| ulRC == MCIERR_SUCCESS ? "SUCCESS" : "FAIL" ); |
| |
| if ( ulRC != MCIERR_SUCCESS ) |
| { |
| pAData->stMCIMixSetup.ulBitsPerSample = 0; |
| return _MCIError( "MCI_MIXSETUP", ulRC ); |
| } |
| |
| if (this->spec.samples == 0 || new_freq == TRUE) { |
| /* also see SDL_audio.c:prepare_audiospec() */ |
| /* Pick a default of ~46 ms at desired frequency */ |
| Uint32 samples = (this->spec.freq / 1000) * 46; |
| Uint32 power2 = 1; |
| while (power2 < samples) { |
| power2 <<= 1; |
| } |
| this->spec.samples = power2; |
| } |
| /* Update the fragment size as size in bytes */ |
| SDL_CalculateAudioSpec( &this->spec ); |
| |
| // Allocate memory buffers |
| |
| stMCIBuffer.ulBufferSize = this->spec.size;// (this->spec.freq / 1000) * 100; |
| stMCIBuffer.ulNumBuffers = NUM_BUFFERS; |
| stMCIBuffer.pBufList = &pAData->aMixBuffers; |
| |
| ulRC = mciSendCommand( pAData->usDeviceId, MCI_BUFFER, |
| MCI_WAIT | MCI_ALLOCATE_MEMORY, &stMCIBuffer, 0 ); |
| if ( ulRC != MCIERR_SUCCESS ) |
| { |
| return _MCIError( "MCI_BUFFER", ulRC ); |
| } |
| pAData->cMixBuffers = stMCIBuffer.ulNumBuffers; |
| this->spec.size = stMCIBuffer.ulBufferSize; |
| |
| // Fill all device buffers with data |
| |
| for( ulIdx = 0; ulIdx < stMCIBuffer.ulNumBuffers; ulIdx++ ) |
| { |
| pAData->aMixBuffers[ulIdx].ulFlags = 0; |
| pAData->aMixBuffers[ulIdx].ulBufferLength = stMCIBuffer.ulBufferSize; |
| pAData->aMixBuffers[ulIdx].ulUserParm = (ULONG)pAData; |
| |
| memset( ((PMCI_MIX_BUFFER)stMCIBuffer.pBufList)[ulIdx].pBuffer, |
| this->spec.silence, stMCIBuffer.ulBufferSize ); |
| } |
| |
| // Write buffers to kick off the amp mixer |
| // pAData->ulQueuedBuf = 1;//stMCIBuffer.ulNumBuffers; |
| ulRC = pAData->stMCIMixSetup.pmixWrite( pAData->stMCIMixSetup.ulMixHandle, |
| &pAData->aMixBuffers, |
| 1 );//stMCIBuffer.ulNumBuffers ); |
| if ( ulRC != MCIERR_SUCCESS ) |
| { |
| _mixIOError( "pmixWrite", ulRC ); |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| |
| static int OS2_Init(SDL_AudioDriverImpl * impl) |
| { |
| /* Set the function pointers */ |
| impl->DetectDevices = OS2_DetectDevices; |
| impl->OpenDevice = OS2_OpenDevice; |
| impl->PlayDevice = OS2_PlayDevice; |
| impl->WaitDevice = OS2_WaitDevice; |
| impl->GetDeviceBuf = OS2_GetDeviceBuf; |
| impl->CloseDevice = OS2_CloseDevice; |
| |
| // TODO: IMPLEMENT CAPTURE SUPPORT: |
| // impl->CaptureFromDevice = ; |
| // impl->FlushCapture = ; |
| // impl->HasCaptureSupport = SDL_TRUE; |
| |
| return 1; /* this audio target is available. */ |
| } |
| |
| |
| AudioBootStrap OS2AUDIO_bootstrap = { "MMOS2", "OS/2 DART", OS2_Init, 0 }; |
| |
| #endif /* SDL_AUDIO_DRIVER_OS2 */ |
| |
| /* vi: set ts=4 sw=4 expandtab: */ |