www.pudn.com > helix.src.0812.rar > audlinux_oss.cpp
/* ***** BEGIN LICENSE BLOCK ***** * Version: RCSL 1.0/RPSL 1.0 * * Portions Copyright (c) 1995-2002 RealNetworks, Inc. All Rights Reserved. * * The contents of this file, and the files included with this file, are * subject to the current version of the RealNetworks Public Source License * Version 1.0 (the "RPSL") available at * http://www.helixcommunity.org/content/rpsl unless you have licensed * the file under the RealNetworks Community Source License Version 1.0 * (the "RCSL") available at http://www.helixcommunity.org/content/rcsl, * in which case the RCSL will apply. You may also obtain the license terms * directly from RealNetworks. You may not use this file except in * compliance with the RPSL or, if you have a valid RCSL with RealNetworks * applicable to this file, the RCSL. Please see the applicable RPSL or * RCSL for the rights, obligations and limitations governing use of the * contents of the file. * * This file is part of the Helix DNA Technology. RealNetworks is the * developer of the Original Code and owns the copyrights in the portions * it created. * * This file, and the files included with this file, is distributed and made * available on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND REALNETWORKS HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS * FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * * Technology Compatibility Kit Test Suite(s) Location: * http://www.helixcommunity.org/content/tck * * Contributor(s): * * ***** END LICENSE BLOCK ***** */ #include#include #include #include #include #include #include #include "ihxpckts.h" #include "hxtick.h" #include "hxprefs.h" #include "timeval.h" #include "hxthread.h" #include "audlinux_oss.h" #include "hxstrutl.h" #include "hxprefutil.h" //we can't set the PCM volume on the PowerPC. The sound driver //only lets up set the master volume. #ifdef __powerpc__ # define HX_VOLUME SOUND_MIXER_VOLUME #else # define HX_VOLUME SOUND_MIXER_PCM #endif //------------------------------------------ // Ctors and Dtors. //------------------------------------------ CAudioOutLinux::CAudioOutLinux() : CAudioOutUNIX(), m_ulTickCount(0), m_ulLastBytesPlayed(0), m_ulLastTimeStamp(0), m_ulPausePosition(0), m_nDevID(NO_FILE_DESCRIPTOR), m_nMixerID(NO_FILE_DESCRIPTOR), m_bGetODelayFailed(TRUE), m_bGetOSpaceFailed(FALSE), m_bTestGetODelay(TRUE) { }; CAudioOutLinux::~CAudioOutLinux() { //The mixer is opened independently of the audio device. Make sure //it is closed. _CloseMixer(); }; //------------------------------------------------------- // These Device Specific methods must be implemented // by the platform specific sub-classes. //------------------------------------------------------- INT16 CAudioOutLinux::_Imp_GetAudioFd(void) { return m_nDevID; } //Devic specific method to set the audio device characteristics. Sample rate, //bits-per-sample, etc. //Method *must* set member vars. m_unSampleRate and m_unNumChannels. HX_RESULT CAudioOutLinux::_SetDeviceConfig( const HXAudioFormat* pFormat ) { if ( m_nDevID < 0 ) return RA_AOE_DEVNOTOPEN; m_wBlockSize = m_ulBytesPerGran; //First, we want to determine the fragment size of the device buffer. //We will let the device determine the number of fragments after we //make sure that each fragment is no bigger than our block size. //The minimum block size for OSS is 16 bytes and the max is 2^0xf. //Basically, Log-base-two(m_wBlockSize)+1. int nPower = 0x4; while( (1< 4 ) nPower--; //Frag info is 0xMMMMSSSS. Where // // MMMM is the total number of fragments. Set this to 7fff if you want // OSS to figure it out. // SSSS is the size of each fragment. The size is 2^0xSSSS. int nFragInfo = 0x7fff0000 | nPower; //Now set the fragment size. if (ioctl(m_nDevID, SNDCTL_DSP_SETFRAGMENT, &nFragInfo) == -1) { return (m_wLastError = RA_AOE_NOTENABLED); } //Now set the format. Either 8-bit or 16-bit audio is supported. int nSampleWidth = pFormat->uBitsPerSample; ULONG32 nSampleRate = pFormat->ulSamplesPerSec; int numChannels = pFormat->uChannels; int nFormat1 = 0; int nFormat2 = 0; if( nSampleWidth == 16) { nFormat1 = nFormat2 = AFMT_S16_NE; } else { nFormat1 = nFormat2 = AFMT_U8; } if(ioctl(m_nDevID, SNDCTL_DSP_SETFMT, &nFormat1) == -1) { return ( m_wLastError = RA_AOE_NOTENABLED ); } //Check and see if the device supports the format we tried to set. //If it didn't take our only other option is unsigned 8 bit. So, if //that is what the device returned just use it. if(nFormat1!=nFormat2 && nFormat1 != AFMT_U8 ) { //Just try to set AFMT_U8. nFormat1 = AFMT_U8; if(ioctl(m_nDevID, SNDCTL_DSP_SETFMT, &nFormat1) == -1) { return ( m_wLastError = RA_AOE_NOTENABLED ); } if( nFormat1 != AFMT_U8 ) { //No know format is supported. return ( m_wLastError = RA_AOE_NOTENABLED ); } } //If we went to 8-bit then if( nFormat1 == AFMT_U8 ) { //AFMT_U8 is set. nSampleWidth = 8; } m_uSampFrameSize = nSampleWidth/8; if ( nSampleWidth != pFormat->uBitsPerSample ) { ((HXAudioFormat*)pFormat)->uBitsPerSample = nSampleWidth; } // Set sampling rate, sample width, #channels. // // Make sure you set num channels before rate. If you don't it will // screw up SBPro devices. Your rate will actually be 1/2 of what // you think it is. //Set number of channels. Stereo or mono. if (ioctl(m_nDevID, SOUND_PCM_WRITE_CHANNELS, &numChannels) == -1) { return ( m_wLastError = RA_AOE_NOTENABLED ); } m_unNumChannels = numChannels; if ( numChannels != pFormat->uChannels ) { ((HXAudioFormat*)pFormat)->uChannels = numChannels; } //Set the sample rate. if (ioctl(m_nDevID, SOUND_PCM_WRITE_RATE, &nSampleRate) == -1) { return ( m_wLastError = RA_AOE_NOTENABLED ); } if (nSampleRate == 0) { /* * Some drivers actually set the sample rate on the device, but * return 0 for the sample rate. On these platforms we just ignore * the return value and assume the sample rate is set to what was * requested. */ nSampleRate = pFormat->ulSamplesPerSec; } m_unSampleRate = nSampleRate; if ( nSampleRate != pFormat->ulSamplesPerSec ) { ((HXAudioFormat*)pFormat)->ulSamplesPerSec = nSampleRate; } //Find out if the user wants to support old OSS drivers that don't have, //or support well, the iocls needed for good syncing. SNDCTL_DSP_GETOSPACE and //SNDCTL_DSP_GETODELAY. IHXPreferences* pPreferences = NULL; if( m_pContext && HXR_OK == m_pContext->QueryInterface( IID_IHXPreferences, (void **) &pPreferences)) { UINT32 nOldOSS = 0; if (HXR_OK == ReadPrefINT32(pPreferences, "SoundDriver", nOldOSS) && (nOldOSS == 1)) { m_bGetODelayFailed = TRUE; m_bGetOSpaceFailed = TRUE; m_bTestGetODelay = FALSE; } HX_RELEASE( pPreferences ); } //for now, PowerPC linux doesn't support the following ioctl call //So, I will just make up some buffer size and live with it for //now. You won't be able to tell the difference until you turn //off threaded audio, then it will be block city! #ifndef __powerpc__ audio_buf_info getYourInfoHere; if( !m_bGetOSpaceFailed ) { //This call is to get how the device is set up after we have //set the sample rate etc. We use this to set up the buffers //we use for the fake pause/resume features. if (ioctl(m_nDevID, SNDCTL_DSP_GETOSPACE, &getYourInfoHere) == -1) { m_wLastError = RA_AOE_NOTENABLED; return m_wLastError; } m_ulDeviceBufferSize = getYourInfoHere.fragsize*getYourInfoHere.fragstotal; } else { //We don't have anyway to determine how big the buffer is. //just guess I guess. m_ulDeviceBufferSize = 8192*4; } #else m_ulDeviceBufferSize = 8192*4; #endif #ifdef _DEBUG fprintf( stderr, "Device Configured:\n"); fprintf( stderr, " Sample Rate: %d\n", m_unSampleRate); fprintf( stderr, " Sample Width: %d\n", nSampleWidth); fprintf( stderr, " Num channels: %d\n", m_unNumChannels); fprintf( stderr, " Block size: %d\n", m_wBlockSize); fprintf( stderr, " Device buffer size: %lu\n", m_ulDeviceBufferSize); fprintf( stderr, " Supports GETOSPACE: %d\n", !m_bGetOSpaceFailed); fprintf( stderr, " Supports GETODELAY: %d\n", !m_bGetODelayFailed); #endif return RA_AOE_NOERR; } void CAudioOutLinux::_SyncUpTimeStamps(ULONG32 lCount) { int bytes2 = 0; int theErr = -1; if( m_bTestGetODelay || !m_bGetODelayFailed ) { HX_ASSERT(m_nDevID ); theErr = ::ioctl(m_nDevID, SNDCTL_DSP_GETODELAY, &bytes2); } if( theErr != -1) { if (m_bTestGetODelay && (bytes2 != 0)) { // We've now seen SNDCTL_DSP_GETODELAY return // a non-zero value so we know it is working m_bTestGetODelay = FALSE; m_bGetODelayFailed = FALSE; } if (!m_bTestGetODelay) { m_ulLastBytesPlayed = (UINT64)(m_ulTotalWritten+lCount-bytes2); m_ulLastTimeStamp = GetTickCount(); } } else { //so we don't try it again. m_bGetODelayFailed = TRUE; m_bTestGetODelay = FALSE; } } //Device specific method to write bytes out to the audiodevice and return a //count of bytes written. HX_RESULT CAudioOutLinux::_WriteBytes( UCHAR* buffer, ULONG32 ulBuffLength, LONG32& lCount ) { HX_RESULT retCode = RA_AOE_NOERR; if( m_nDevID < 0 ) { retCode = RA_AOE_DEVNOTOPEN; } else { if( m_ulTickCount == 0 ) m_ulTickCount = GetTickCount(); lCount = ::write( m_nDevID, buffer, ulBuffLength); if( lCount < 0 ) { //Error occurred. if( errno == EAGAIN ) retCode = RA_AOE_NOERR; if( errno == EINTR ) retCode = RA_AOE_DEVBUSY; } else { _SyncUpTimeStamps(lCount); } } return retCode; } //Device specific methods to open/close the mixer and audio devices. HX_RESULT CAudioOutLinux::_OpenAudio() { HX_RESULT retCode = RA_AOE_NOERR; //Set the tick count to zero m_ulTickCount = 0; m_ulLastTimeStamp = 0; m_ulLastBytesPlayed = 0; m_ulPausePosition = 0; //Check the environmental variable to let user overide default device. char *pszOverrideName = getenv( "AUDIO" ); /* Flawfinder: ignore */ char szDevName[MAX_DEV_NAME]; /* Flawfinder: ignore */ // Use defaults if no environment variable is set. if ( pszOverrideName && strlen(pszOverrideName)>0 ) { SafeStrCpy( szDevName, pszOverrideName, MAX_DEV_NAME ); } else { SafeStrCpy( szDevName, "/dev/dsp", MAX_DEV_NAME ); } // Open the audio device if it isn't already open if ( m_nDevID < 0 ) { m_nDevID = ::open( szDevName, O_WRONLY ); } if ( m_nDevID < 0 ) { #ifdef _DEBUG fprintf( stderr, "Failed to open audio(%s)!!!!!!! Code is: %d errno: %d\n", szDevName, m_nDevID, errno ); #endif //Error opening device. retCode = RA_AOE_BADOPEN; } m_wLastError = retCode; return m_wLastError; } HX_RESULT CAudioOutLinux::_CloseAudio() { HX_RESULT retCode = RA_AOE_NOERR; if( m_nDevID >= 0 ) { ::close( m_nDevID ); m_nDevID = NO_FILE_DESCRIPTOR; } else { retCode = RA_AOE_DEVNOTOPEN; } m_wLastError = retCode; return m_wLastError; } HX_RESULT CAudioOutLinux::_OpenMixer() { HX_RESULT retCode = RA_AOE_NOERR; if(!m_bMixerPresent) { //Let user override default device with environ variable. char *pszOverrideName = getenv( "MIXER" ); /* Flawfinder: ignore */ char szDevCtlName[MAX_DEV_NAME]; /* Flawfinder: ignore */ if (pszOverrideName && strlen(pszOverrideName)>0 ) { SafeStrCpy( szDevCtlName , pszOverrideName, MAX_DEV_NAME ); } else { SafeStrCpy( szDevCtlName , "/dev/mixer", MAX_DEV_NAME ); // default for volume } m_nMixerID = ::open( szDevCtlName, O_RDWR ); if (m_nMixerID > 0) { m_bMixerPresent = 1; _Imp_GetVolume(); } else { m_nMixerID = NO_FILE_DESCRIPTOR; m_bMixerPresent = 0; } } m_wLastError = retCode; return m_wLastError; } HX_RESULT CAudioOutLinux::_CloseMixer() { HX_RESULT retCode = RA_AOE_NOERR; if( m_nMixerID >= 0 ) { ::close( m_nMixerID ); m_nMixerID = NO_FILE_DESCRIPTOR; } m_wLastError = retCode; return m_wLastError; } //Device specific method to reset device and return it to a state that it //can accept new sample rates, num channels, etc. HX_RESULT CAudioOutLinux::_Reset() { HX_RESULT retCode = RA_AOE_NOERR; m_ulPausePosition = 0; if ( m_nDevID < 0 ) { retCode = RA_AOE_DEVNOTOPEN; } else if( ::ioctl (m_nDevID, SOUND_PCM_RESET, 0) == -1 ) { retCode = RA_AOE_GENERAL; } m_wLastError = retCode; return m_wLastError; } //Device specific method to get/set the devices current volume. UINT16 CAudioOutLinux::_GetVolume() const { int nVolume = 0; int nRetVolume = 0; int nLeftVolume = 0; int nRightVolume = 0; if (::ioctl( m_nMixerID, MIXER_READ(HX_VOLUME), &nVolume) < 0) { nRetVolume = 0; } nLeftVolume = (nVolume & 0x000000ff); nRightVolume = (nVolume & 0x0000ff00) >> 8; //Which one to use? Average them? nRetVolume = nLeftVolume ; return nRetVolume; } HX_RESULT CAudioOutLinux::_SetVolume(UINT16 unVolume) { HX_RESULT retCode = RA_AOE_NOERR; int nNewVolume=0; //Set both left and right volumes. nNewVolume = (unVolume & 0xff) | ((unVolume &0xff) << 8); if (::ioctl( m_nMixerID, MIXER_WRITE(HX_VOLUME), &nNewVolume) < 0) { retCode = RA_AOE_NOTSUPPORTED; } m_wLastError = retCode; return m_wLastError; } //Device specific method to drain a device. This should play the remaining //bytes in the devices buffer and then return. HX_RESULT CAudioOutLinux::_Drain() { HX_RESULT retCode = RA_AOE_NOERR; if ( m_nDevID < 0 ) { retCode = RA_AOE_DEVNOTOPEN; } else if( ::ioctl (m_nDevID, SNDCTL_DSP_SYNC, 0) == -1 ) { retCode = RA_AOE_GENERAL; } m_wLastError = retCode; return m_wLastError; } UINT64 CAudioOutLinux::_GetBytesActualyPlayed(void) const { /* Get current playback position in device DMA. */ int bytes2 = 0; UINT64 ulTheAnswer = 0; //What versions of the linux kernel do we want to support? if( !m_bGetODelayFailed ) { if( m_ulTotalWritten > 0 ) { HX_ASSERT( m_unSampleRate!=0 && m_uSampFrameSize!=0 ); ULONG32 ulTick = GetTickCount(); //We need to update the timestamps every so often. //This make sure that if the XServer was blocked, and //we ran dry, that we re-sync up. if( (ulTick-m_ulLastTimeStamp)>200 ) { ((CAudioOutLinux*)this)->_SyncUpTimeStamps(); ulTick = GetTickCount(); } ulTheAnswer = (UINT64)(m_ulLastBytesPlayed+ ((float)(ulTick-m_ulLastTimeStamp)* (float)m_unNumChannels/1000.0* m_unSampleRate*m_uSampFrameSize) +0.5 ); } } else { //We will assume that the error is because of an incomplete //implementation of the oss compatible driver. So, just //fake it with time stamps. if( m_ulTotalWritten > 0 ) { ulTheAnswer = (UINT64)((float)(GetTickCount()-m_ulTickCount)*(float)m_unNumChannels/1000.0*m_unSampleRate*m_uSampFrameSize); ulTheAnswer += m_ulPausePosition; } } return ulTheAnswer; } //this must return the number of bytes that can be written without blocking. //Don't use SNDCTL_DSP_GETODELAY here as it can't compute that amount //correctly. HX_RESULT CAudioOutLinux::_GetRoomOnDevice(ULONG32& ulBytes) const { HX_RESULT retCode = RA_AOE_NOERR; audio_buf_info stBuffInfo; int theErr=0; //What versions of the linux kernel do we want to support? if( m_bGetOSpaceFailed ) { theErr = -1; } else { theErr = ::ioctl(m_nDevID, SNDCTL_DSP_GETOSPACE, &stBuffInfo); } if ( theErr != -1 ) { ulBytes = stBuffInfo.bytes; } else { //So we just try it once. m_bGetOSpaceFailed = TRUE; ulBytes = m_ulDeviceBufferSize-(m_ulTotalWritten-_GetBytesActualyPlayed() ); } m_wLastError = retCode; return m_wLastError; } HX_RESULT CAudioOutLinux::_CheckFormat( const HXAudioFormat* pFormat ) { int nBitsPerSample = pFormat->uBitsPerSample; int ulTmp = pFormat->ulSamplesPerSec; int nNumChannels = pFormat->uChannels; float fTmp = 0.0; HX_RESULT retCode = RA_AOE_NOERR; //Is the device already open? if( m_nDevID > 0 || RA_AOE_NOERR != _OpenAudio() ) { retCode = RA_AOE_DEVBUSY; return retCode; } //See if the sample rate is supported. if (ioctl(m_nDevID, SOUND_PCM_WRITE_RATE, &ulTmp) == -1) { //Not quite the real error, but it is what we need to return. retCode = RA_AOE_DEVBUSY; goto donechecking; } if (ulTmp == 0) { /* * Some drivers actually set the sample rate on the device, but * return 0 for the sample rate. On these platforms we just ignore * the return value and assume the sample rate is set to what was * requested. */ ulTmp = pFormat->ulSamplesPerSec; } //The ESS 1688 Sound card (not the ESS Solo-1) will return sample //rates that are close but not quite the ones we asked for (the //ESS Solo-1 doesn't play mono). I have see freqs like 44194 //instead of 44100 (.2%) and 7984 instead of 8000 (.6%). //So, if we are close enough just say it is OK. //How about 1%? fTmp = (float)ulTmp/(float)pFormat->ulSamplesPerSec; if( fabs(1.0-fTmp) > .01 ) { //It is NOT supported retCode = RA_AOE_BADFORMAT; goto donechecking; } //Check num channels. if (ioctl(m_nDevID, SOUND_PCM_WRITE_CHANNELS, &nNumChannels) == -1) { retCode = RA_AOE_DEVBUSY; goto donechecking; } else if ( nNumChannels != pFormat->uChannels ) { retCode = RA_AOE_BADFORMAT; goto donechecking; } //Check the frame size. if (ioctl(m_nDevID, SNDCTL_DSP_SETFMT, &nBitsPerSample) == -1) { retCode = RA_AOE_DEVBUSY; goto donechecking; } else if ( nBitsPerSample != pFormat->uBitsPerSample ) { retCode = RA_AOE_BADFORMAT; goto donechecking; } //Close the audio device. donechecking: _CloseAudio(); m_wLastError = retCode; return retCode; } HX_RESULT CAudioOutLinux::_CheckSampleRate( ULONG32 ulSampleRate ) { ULONG32 ulTmp = ulSampleRate; m_wLastError = RA_AOE_NOERR; //Is the device already open? if( m_nDevID > 0 || RA_AOE_NOERR != _OpenAudio() ) { m_wLastError = RA_AOE_DEVBUSY; } else { //See if the sample rate is supported. if (ioctl(m_nDevID, SOUND_PCM_WRITE_RATE, &ulTmp) == -1) { //Not quite the real error, but it is what we need to return. m_wLastError = RA_AOE_DEVBUSY; } else if( ulSampleRate != ulTmp ) { //It is NOT supported m_wLastError = RA_AOE_BADFORMAT; } _CloseAudio(); } return m_wLastError; } HX_RESULT CAudioOutLinux::_Pause() { m_wLastError = HXR_OK; m_ulPausePosition = m_ulTotalWritten; m_ulTickCount = 0; m_ulLastTimeStamp = 0; return m_wLastError; } HX_RESULT CAudioOutLinux::_Resume() { m_wLastError = HXR_OK; if( m_ulTotalWritten > 0 ) { m_ulTickCount = GetTickCount(); m_ulLastTimeStamp = m_ulTickCount; } return m_wLastError; }