www.pudn.com > helix.src.0812.rar > audUnix.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 "hxcom.h" #include "hxresult.h" #include "hxengin.h" #include "ihxpckts.h" // for IHXBuffer #include "hxslist.h" #include "timeval.h" #include "hxausvc.h" #include "auderrs.h" #include "hxaudev.h" #include "hxaudses.h" #include "hxtick.h" #include "chxpckts.h" #include "debug.h" #include "microsleep.h" //me. #include "audUnix.h" #include#if defined(_THREADED_AUDIO) && defined(_UNIX_THREADS_SUPPORTED) #include "hxprefs.h" #endif //-1 is usually considered to be no file descriptor. const int CAudioOutUNIX::NO_FILE_DESCRIPTOR = -1; const int CAudioOutUNIX::MAX_VOLUME = 100; //XXXgfw We need to clean up the return values. We need to return only PN result codes //XXXgfw and not RA_AOE codes from interface methods. CAudioOutUNIX::CAudioOutUNIX() : m_pCallback(NULL), m_wState( RA_AOS_CLOSED ), m_wLastError( RA_AOE_NOERR ), m_bMixerPresent(FALSE), m_wBlockSize(0), m_ulLastNumBytes (0), m_ulTotalWritten(0), m_bFirstWrite (TRUE), m_pPlaybackCountCBTime(0), m_PendingCallbackID (0), m_bCallbackPending(FALSE), m_pWriteList(NULL), m_ulDeviceBufferSize(0), #if defined(_THREADED_AUDIO) && defined(_UNIX_THREADS_SUPPORTED) m_mtxWriteListPlayStateLock(NULL), m_mtxDeviceStateLock(NULL), m_audioThread(NULL), m_bUserWantsThreads(TRUE), m_ulSleepTime(0), #endif m_pRollbackBuffer(NULL) { //Alloc a Timeval for use with the timer callback. m_pPlaybackCountCBTime = new Timeval; m_pCallback = new HXPlaybackCountCB(this); m_pCallback->AddRef(); //Allco our write buffer list. Want to throw from here? You will, like //it or not. m_pWriteList = new CHXSimpleList(); } void CAudioOutUNIX::_initAfterContext() { #if defined(_THREADED_AUDIO) && defined(_UNIX_THREADS_SUPPORTED) HX_ASSERT( m_pContext ); //Find out if the user wants to use threads or not. IHXPreferences* pPreferences = NULL; if( m_pContext && HXR_OK == m_pContext->QueryInterface( IID_IHXPreferences, (void **) &pPreferences)) { IHXBuffer *pBuffer = NULL; pPreferences->ReadPref("ThreadedAudio", pBuffer); if (pBuffer) { m_bUserWantsThreads = (::atoi((const char*)pBuffer->GetBuffer()) == 1); HX_RELEASE(pBuffer); } HX_RELEASE( pPreferences ); } if( m_bUserWantsThreads ) { //Initialize the write list and playstate mutex. HXMutex::MakeMutex(m_mtxWriteListPlayStateLock ); HXMutex::MakeMutex( m_mtxDeviceStateLock ); HXThread::MakeThread( m_audioThread ); } #endif } CAudioOutUNIX::~CAudioOutUNIX() { //We must assume that _Imp_Close has already been called. If not, we are //in big trouble. if ( m_wState != RA_AOS_CLOSED ) { HX_ASSERT( "Device not closed in dtor." == NULL ); } #if defined(_THREADED_AUDIO) && defined(_UNIX_THREADS_SUPPORTED) //Sanity check that the audio thread has exited. //XXXgfw ADD a thread join to HXThread and use it instead // of the cond var below in _close and we won't have // to check here at all. #ifdef _DEBUG UINT32 unThreadID = 0; if( m_audioThread ) { m_audioThread->GetThreadId(unThreadID); HX_ASSERT( unThreadID == 0 ); } #endif #endif //Clean up the scheduler. HX_RELEASE( m_pScheduler ); //Just in case it isn't empty at this point. while( m_pWriteList && !m_pWriteList->IsEmpty() ) { IHXBuffer* pBuffer = (IHXBuffer *)(m_pWriteList->RemoveHead()); HX_RELEASE( pBuffer ); } HX_DELETE(m_pPlaybackCountCBTime); HX_RELEASE(m_pCallback); HX_DELETE(m_pWriteList); HX_VECTOR_DELETE(m_pRollbackBuffer); #if defined(_THREADED_AUDIO) && defined(_UNIX_THREADS_SUPPORTED) if( m_bUserWantsThreads ) { HX_DELETE( m_mtxWriteListPlayStateLock ); HX_DELETE( m_mtxDeviceStateLock ); HX_DELETE( m_audioThread ); } #endif } UINT16 CAudioOutUNIX::_Imp_GetVolume() { if (!m_bMixerPresent) _OpenMixer(); if ( m_bMixerPresent ) { m_uCurVolume = _GetVolume(); } return m_uCurVolume; } HX_RESULT CAudioOutUNIX::_Imp_SetVolume( const UINT16 uVolume ) { HX_RESULT retCode = RA_AOE_NOERR; //Mixer methods can be called before _Imp_Open if( !m_bMixerPresent ) _OpenMixer(); //m_uCurVolume is set up at the pnaudev level. if( m_bMixerPresent ) { retCode =_SetVolume( uVolume ); } m_wLastError = retCode; return m_wLastError; } BOOL CAudioOutUNIX::_Imp_SupportsVolume() { return TRUE; } HX_RESULT CAudioOutUNIX::_Imp_Open( const HXAudioFormat* pFormat ) { HX_RESULT retCode = RA_AOE_NOERR; //Schedule the timer callback... if(m_pContext && !m_pScheduler) { m_pContext->QueryInterface(IID_IHXScheduler, (void**) &m_pScheduler ); } // Check state. Could already be opened. if( !IsOpen() && m_wState != RA_AOS_OPENING ) { retCode = _OpenAudio(); if( retCode == RA_AOE_NOERR ) { m_wBlockSize = m_ulBytesPerGran; m_uSampFrameSize = pFormat->uBitsPerSample / 8; //Set device state LOCK(m_mtxWriteListPlayStateLock); m_wState = RA_AOS_OPENING; UNLOCK(m_mtxWriteListPlayStateLock); //Configure the audio device. retCode = _SetDeviceConfig( pFormat ); if (retCode != RA_AOE_NOERR) { _CloseAudio(); m_wState=RA_AOS_CLOSED; } else { #if defined(_THREADED_AUDIO) && defined(_UNIX_THREADS_SUPPORTED) //We want to sleep as a function of device buffer size. //If we have a small m_ulDeviceBufferSize we can only //afford to sleep just a little while. HX_ASSERT( m_ulDeviceBufferSize != 0 ); m_ulSleepTime = (((float)m_ulDeviceBufferSize/(float)m_uSampFrameSize)/ (float)m_unSampleRate) * 1000 / (float)m_unNumChannels; #endif if (!m_bMixerPresent) _OpenMixer(); if( _IsSelectable() ) { IHXAsyncIOSelection* pAsyncIO = NULL; if( m_pContext && HXR_OK == m_pContext->QueryInterface( IID_IHXAsyncIOSelection, (void**)&pAsyncIO)) { pAsyncIO->Add( new HXPlaybackCountCB(this, FALSE), _Imp_GetAudioFd() , PNAIO_WRITE); HX_RELEASE( pAsyncIO ); } } } } else { //Couldn't open the audio device. //Just pass through and return the error. m_wState=RA_AOS_CLOSED; } } // If we're optimizing/minimizing heap, we don't want to do this malloc // yet, because we're going to change/minize the value of // m_ulDeviceBufferSize later on. #ifndef HELIX_CONFIG_MIN_ROLLBACK_BUFFER //_SetDeviceConfig should have set the m_ulDeviceBufferSize var. if( RA_AOE_NOERR == retCode && !_HardwarePauseSupported() ) { HX_ASSERT( m_ulDeviceBufferSize != 0 ); if( NULL == m_pRollbackBuffer) { m_pRollbackBuffer = new UCHAR[m_ulDeviceBufferSize]; memset( m_pRollbackBuffer, '0', m_ulDeviceBufferSize ); } } #endif // HELIX_CONFIG_MIN_ROLLBACK_BUFFER #if defined(_THREADED_AUDIO) && defined(_UNIX_THREADS_SUPPORTED) //Start up the audio thread if it isn't going allready from a previous open //which sometimes happens. //Make sure we only have one thread going at a time. if( m_bUserWantsThreads && RA_AOE_NOERR==retCode ) { UINT32 unThreadID = 0; m_audioThread->GetThreadId(unThreadID); if( unThreadID == 0 ) m_audioThread->CreateThread( CAudioOutUNIX::AudioThread, this, 0 ); m_audioThread->GetThreadId(unThreadID); } #endif m_wLastError = retCode; return m_wLastError; } HX_RESULT CAudioOutUNIX::_Imp_Close() { HX_RESULT retCode = RA_AOE_NOERR; LOCK(m_mtxWriteListPlayStateLock); m_wState = RA_AOS_CLOSING; UNLOCK(m_mtxWriteListPlayStateLock); #if defined(_THREADED_AUDIO) && defined(_UNIX_THREADS_SUPPORTED) //The audio thread should exit when the state changes to closed. //Wait for it to do so and clean up. if( m_bUserWantsThreads ) { m_audioThread->Exit(0); } #endif //Reset the device and empty the write buffer. //Don't protect this reset with a lock as the audio thread //has alread exited. retCode = _Imp_Reset(); if( retCode != RA_AOE_DEVNOTOPEN ) { if( _IsSelectable() ) { IHXAsyncIOSelection* pAsyncIO = NULL; if( HXR_OK == m_pContext->QueryInterface( IID_IHXAsyncIOSelection, (void**)&pAsyncIO) ) { pAsyncIO->Remove(_Imp_GetAudioFd() , PNAIO_WRITE); HX_RELEASE( pAsyncIO ); } } } //Close the audio device. retCode = _CloseAudio(); //Close the mixer device. _CloseMixer(); //Set state. LOCK(m_mtxWriteListPlayStateLock); m_wState = RA_AOS_CLOSED; UNLOCK(m_mtxWriteListPlayStateLock); //Remove callback from scheduler if (m_bCallbackPending) { m_pScheduler->Remove(m_PendingCallbackID); m_bCallbackPending = FALSE; } HX_VECTOR_DELETE(m_pRollbackBuffer); //return m_wLastError = retCode; return m_wLastError; } //So far no UN*X platforms support this. HX_RESULT CAudioOutUNIX::_Imp_Seek(ULONG32 ulSeekTime) { m_wLastError = HXR_OK; return m_wLastError; } HX_RESULT CAudioOutUNIX::_Imp_Pause() { //XXXgfw We really should be closing the device instead of keeping it open to be nice to //other procs. LOCK(m_mtxWriteListPlayStateLock); m_wState = RA_AOS_OPEN_PAUSED; UNLOCK(m_mtxWriteListPlayStateLock); if( !_HardwarePauseSupported() ) { // Strategy. //--Find out how much we have left in the device's buffer. ULONG32 ulBytesPlayed = _GetBytesActualyPlayed(); ULONG32 ulNumBytesToRewind = m_ulTotalWritten-ulBytesPlayed; //Sometimes we can get a value for bytesPlayed that is slightly //more then the amount we have actually written. This results //in a huge number. if( ulBytesPlayed > m_ulTotalWritten ) ulNumBytesToRewind = 0; //--and reset player and discard all the data in the device's buffer //We will just ignore an error here. That means the buffer will just drain //and we will hear it again when they unpause. LOCK(m_mtxDeviceStateLock); _Reset(); UNLOCK(m_mtxDeviceStateLock); //Make sure we only deal with full samples. Bytes-per-sample*num-channels. int nRem = ulNumBytesToRewind%(m_uSampFrameSize*m_unNumChannels); ulNumBytesToRewind = ulNumBytesToRewind>nRem ? ulNumBytesToRewind-nRem : 0; // In heap-optimized mode, we accept that we're going to lose a // little bit of data after a pause/resume because we are using as // small a rollback buffer as we can get away with. #ifdef HELIX_CONFIG_MIN_ROLLBACK_BUFFER if( ulNumBytesToRewind > m_ulDeviceBufferSize ) { ulNumBytesToRewind = m_ulDeviceBufferSize; } #endif // HELIX_CONFIG_MIN_ROLLBACK_BUFFER // and add it to the front of the write buffer. IHXBuffer* pNewBuffer = new CHXBuffer(); pNewBuffer->Set( m_pRollbackBuffer+m_ulDeviceBufferSize-ulNumBytesToRewind, ulNumBytesToRewind ); LOCK(m_mtxWriteListPlayStateLock); m_pWriteList->AddHead(pNewBuffer); pNewBuffer->AddRef(); UNLOCK(m_mtxWriteListPlayStateLock); //--Subtract that from m_ulTotalWritten. m_ulTotalWritten -= ulNumBytesToRewind; _Pause(); } else { //The hardware device handles the Pause/Resume. LOCK(m_mtxDeviceStateLock); _Pause(); UNLOCK(m_mtxDeviceStateLock); } m_wLastError = HXR_OK; return m_wLastError; } HX_RESULT CAudioOutUNIX::_Imp_Resume() { //XXXgfw We really should be closing and re-opening the device to be nice to other procs. LOCK(m_mtxWriteListPlayStateLock); m_wState = RA_AOS_OPEN_PLAYING; UNLOCK(m_mtxWriteListPlayStateLock); //XXXgfw If the two branches of the if are the same maybe get rid of one???? if( !_HardwarePauseSupported() ) { _Resume(); _Imp_Write(NULL); } else { //The hardware device handles the Pause/Resume. _Resume(); _Imp_Write(NULL); } m_wLastError = HXR_OK; return m_wLastError; } HX_RESULT CAudioOutUNIX::_Imp_Reset() { HX_RESULT retCode = RA_AOE_NOERR; if ( m_wState != RA_AOS_CLOSED ) { LOCK(m_mtxDeviceStateLock); retCode = _Reset(); UNLOCK(m_mtxDeviceStateLock); LOCK(m_mtxWriteListPlayStateLock); while (m_pWriteList && m_pWriteList->GetCount() > 0) { IHXBuffer* pBuffer = (IHXBuffer *)(m_pWriteList->RemoveHead()); HX_RELEASE( pBuffer ); } UNLOCK(m_mtxWriteListPlayStateLock); m_ulTotalWritten = 0; m_bFirstWrite = TRUE; m_ulLastNumBytes = 0; } m_wLastError = retCode; return m_wLastError; } HX_RESULT CAudioOutUNIX::_Imp_Drain() { HX_RESULT retCode = RA_AOE_NOERR; LOCK(m_mtxWriteListPlayStateLock); if( m_wState != RA_AOS_CLOSED ) { retCode = _Drain(); } while( m_pWriteList && !m_pWriteList->IsEmpty() ) { IHXBuffer* pBuffer = (IHXBuffer *)(m_pWriteList->RemoveHead()); HX_RELEASE( pBuffer ); } UNLOCK(m_mtxWriteListPlayStateLock); m_wLastError = retCode; return m_wLastError; } HX_RESULT CAudioOutUNIX::_Imp_CheckFormat( const HXAudioFormat* pFormat ) { HX_RESULT retCode = RA_AOE_NOERR; m_wLastError = HXR_OK; retCode = _CheckFormat( pFormat ); if( RA_AOE_NOERR != retCode && RA_AOE_DEVBUSY != retCode ) { m_wLastError = HXR_FAILED; } else { m_wLastError = HXR_OK; } return m_wLastError; } HX_RESULT CAudioOutUNIX::_Imp_GetCurrentTime( ULONG32& ulCurrentTime ) { ULONG32 ulTime = 0; UINT64 ulBytes = 0; LOCK(m_mtxWriteListPlayStateLock); ulBytes = _GetBytesActualyPlayed(); UNLOCK(m_mtxWriteListPlayStateLock); ulTime = (ULONG32)(( ( (double)ulBytes/(double)m_uSampFrameSize)/(double)m_unSampleRate) * 1000.0 / (double)m_unNumChannels); //Not used anywhere but belongs to CHXAudioDevice so we must set it. m_ulCurrentTime = ulTime; //Set the answer. ulCurrentTime = ulTime; m_wLastError = HXR_OK; return HXR_OK; } void CAudioOutUNIX::DoTimeSyncs() { ReschedPlaybackCheck(); OnTimeSync(); return; } HX_RESULT CAudioOutUNIX::ReschedPlaybackCheck() { HX_RESULT retCode = HXR_OK; if(!m_bCallbackPending) { HX_ASSERT( m_pCallback ); if(m_pCallback) { *m_pPlaybackCountCBTime += (int)(500*m_ulGranularity); m_bCallbackPending = TRUE; m_PendingCallbackID = m_pScheduler->AbsoluteEnter( m_pCallback,*((HXTimeval*)m_pPlaybackCountCBTime)); } else { retCode = HXR_OUTOFMEMORY; } } m_wLastError = retCode; return m_wLastError; } UINT16 CAudioOutUNIX::_NumberOfBlocksRemainingToPlay(void) { UINT32 bytesBuffered = 0; //We have to go through all of the buffers and count the size. Even though //we always write m_wBlockSize buffers, we can get off-sized buffs because //of the pause/resume code. When we pause we rewind however many bytes have //not been played yet in the buffer. LOCK(m_mtxWriteListPlayStateLock); if( m_pWriteList ) { LISTPOSITION i = m_pWriteList->GetHeadPosition(); while( i ) { bytesBuffered += ((IHXBuffer *)m_pWriteList->GetAt(i)) -> GetSize(); m_pWriteList->GetNext(i); } } bytesBuffered += (m_ulTotalWritten - _GetBytesActualyPlayed()); UNLOCK(m_mtxWriteListPlayStateLock); return bytesBuffered / m_wBlockSize + 1; } CAudioOutUNIX::HXPlaybackCountCB::~HXPlaybackCountCB() { } STDMETHODIMP CAudioOutUNIX::HXPlaybackCountCB::QueryInterface( REFIID riid, void** ppvObj ) { HX_RESULT retCode = HXR_OK; if( IsEqualIID(riid, IID_IHXCallback) ) { AddRef(); *ppvObj = (IHXCallback*)this; } else if (IsEqualIID(riid, IID_IUnknown)) { AddRef(); *ppvObj = this; } else { *ppvObj = NULL; retCode = HXR_NOINTERFACE; } return retCode; } STDMETHODIMP_(ULONG32) CAudioOutUNIX::HXPlaybackCountCB::AddRef() { return InterlockedIncrement(&m_lRefCount); } STDMETHODIMP_(ULONG32) CAudioOutUNIX::HXPlaybackCountCB::Release() { if (InterlockedDecrement(&m_lRefCount) > 0) { return m_lRefCount; } delete this; return HXR_OK; } STDMETHODIMP CAudioOutUNIX::HXPlaybackCountCB::Func(void) { if (m_pAudioDeviceObject) { if(!m_timed) { //m_pAudioDeviceObject->_Imp_Write(NULL); } else { m_pAudioDeviceObject->m_bCallbackPending = FALSE; m_pAudioDeviceObject->_Imp_Write(NULL); m_pAudioDeviceObject->DoTimeSyncs(); } } return HXR_OK; } HX_RESULT CAudioOutUNIX::_Pause() { return RA_AOE_NOTSUPPORTED; } HX_RESULT CAudioOutUNIX::_Resume() { return RA_AOE_NOTSUPPORTED; } BOOL CAudioOutUNIX::_IsSelectable() const { return TRUE; } BOOL CAudioOutUNIX::_HardwarePauseSupported() const { return FALSE; } #if defined(_THREADED_AUDIO) && defined(_UNIX_THREADS_SUPPORTED) void* CAudioOutUNIX::AudioThread(void *thisPointer ) { BOOL bReadyToExit=FALSE; CAudioOutUNIX* that = (CAudioOutUNIX*)thisPointer; while(!bReadyToExit) { //If you want to destroy frame rate just sit on this lock for //a while. If you don't, make sure this is a *very* fast loop. that->m_mtxWriteListPlayStateLock->Lock(); that->m_mtxDeviceStateLock->Lock(); if(that->m_wState!=RA_AOS_CLOSED && that->m_wState!=RA_AOS_CLOSING) { if( that->m_pWriteList->GetCount() > 0 && that->m_wState == RA_AOS_OPEN_PLAYING ) { that->_PushBits(); } } else { bReadyToExit=TRUE; } that->m_mtxDeviceStateLock->Unlock(); that->m_mtxWriteListPlayStateLock->Unlock(); //OK, sleep the amount of time it takes to play 1/4 of the device's buffer. microsleep(that->m_ulSleepTime/4); } //Signal the parent thread that we are done. that->m_audioThread->Exit(0); return (void*)0; } #endif // Don't protect anything in this method with mutexes. It has // already been done from where it is called from. But mostly // because we aren't using recursive mutexes yet. ULONG32 CAudioOutUNIX::_PushBits() { IHXBuffer* pBuffer = NULL; UCHAR* pData = 0; ULONG32 ulBufLen = 0; LONG32 lCount = 0; //We are going to try and write. Grab the head and do it. pBuffer = (IHXBuffer*)m_pWriteList->RemoveHead(); pData = pBuffer->GetBuffer(); ulBufLen = pBuffer->GetSize(); // // Now, in case there is no mixer present, do the volume manually. // UCHAR* pNoVolumeData = NULL; if ( !m_bMixerPresent ) { //Save a copy of the non-volume mutated sound data. If we don't, our //rewind buffer will fill up with volume modified data and it will get //run through the manual sound code again. if( !_HardwarePauseSupported() ) { pNoVolumeData = new UCHAR[ ulBufLen ]; memcpy( pNoVolumeData, pData, ulBufLen ); /* Flawfinder: ignore */ } int i, j = ulBufLen / sizeof(short); short *s = (short *)pData; for (i = 0 ; i < j ; i++) { s[i] = ((int)s[i]) * (m_uCurVolume) / MAX_VOLUME; } } UNLOCK(m_mtxWriteListPlayStateLock); _WriteBytes(pData, ulBufLen, lCount); LOCK(m_mtxWriteListPlayStateLock); //Make sure we wrote the whole buffer like we wanted to. if( lCount!=-1 && lCount != ulBufLen) { //In release we can ignore this...I guess. But why didn't we? HX_ASSERT( "We ALWAYS write full buffers!" == NULL ); } // Check for bad write...then check errno; write could be interrupted // by setitimer and sigalarm. If so, loop and try writing again. if ( lCount == -1 ) { //EAGAIN should *never* happen as we are checking to make sure we don't //block on this call.... if ( errno == EAGAIN) { HX_ASSERT( "We shouldn't be blocking here..."==NULL); } } if( lCount > 0 ) { m_ulTotalWritten += lCount; //We only need to do this if the hardware doesn't support pause. if( !_HardwarePauseSupported() ) { // If we wrote to the device we need to keep a copy of the // data our device buffer. We use this to 'rewind' the data // in case we get paused. //If we could write lCount without blocking then there was at //least that much room in the device and since m_pRollbackBuffer //is as large as the devices buffer, we can safely shift and copy. //Add the new stuff to the end pushing the rest of the data forward. //We need this sanity check to prevent a very hard bug to find. //Don't get rid of it! If this asserts it *really* means something. //The first thing to check is the __powerpc defines that determine //buffer size. HX_ASSERT( lCount <= m_ulDeviceBufferSize ); // Ok, now that we've asserted to catch the // bug, let's prevent the crash. if (lCount > m_ulDeviceBufferSize) lCount = m_ulDeviceBufferSize; memmove( m_pRollbackBuffer, m_pRollbackBuffer+lCount, m_ulDeviceBufferSize-lCount); /* Flawfinder: ignore */ //Now copy in the new data at the end of the buffer. if( NULL != pNoVolumeData ) memcpy( m_pRollbackBuffer+m_ulDeviceBufferSize-lCount, pNoVolumeData, lCount ); /* Flawfinder: ignore */ else memcpy( m_pRollbackBuffer+m_ulDeviceBufferSize-lCount, pData, lCount ); /* Flawfinder: ignore */ } } HX_VECTOR_DELETE(pNoVolumeData); HX_RELEASE( pBuffer ); return lCount; } HX_RESULT CAudioOutUNIX::_Imp_Write( const HXAudioData* pAudioOutHdr ) { IHXBuffer* pBuffer = NULL; ULONG32 ulCount = 0; //_Imp_Write won't be called if the device couldn't be opened. But, //Just in case. if( !IsOpen() ) { return RA_AOE_DEVNOTOPEN; } //Schedule callbacks. if( m_bFirstWrite && pAudioOutHdr ) { m_bFirstWrite = FALSE; // Initialize the playback callback time. HXTimeval lTime = m_pScheduler->GetCurrentSchedulerTime(); m_pPlaybackCountCBTime->tv_sec = lTime.tv_sec; m_pPlaybackCountCBTime->tv_usec = lTime.tv_usec; // Scheduler playback callback. ReschedPlaybackCheck(); } //Blindly add the incoming data to the tail of the writelist. //Some audio devices have very small device buffers. If this //buffer is smaller than our incoming block size we must break //up the incoming buffer into smaller chuncks. We want to be //able to fix *at least* two(1.5?) chunks into the buffer so we don't //block all the time. if( pAudioOutHdr != NULL ) { LOCK(m_mtxWriteListPlayStateLock); int nBuffSize = pAudioOutHdr->pData->GetSize(); // In heap-optimized mode, this is where we set the value of // m_ulDeviceBufferSize, and malloc the buffer. #ifdef HELIX_CONFIG_MIN_ROLLBACK_BUFFER if( m_ulDeviceBufferSize != nBuffSize ) { m_ulDeviceBufferSize = nBuffSize; HX_VECTOR_DELETE(m_pRollbackBuffer); m_pRollbackBuffer = new UCHAR[m_ulDeviceBufferSize]; } #endif // HELIX_CONFIG_MIN_ROLLBACK_BUFFER // Shouldn't this be ">=" and not ">"? if( m_ulDeviceBufferSize >= nBuffSize ) { //No need to break it up. IHXBuffer* pTmpBuff = pAudioOutHdr->pData; m_pWriteList->AddTail(pTmpBuff); pTmpBuff->AddRef(); } else { //Break it up. First find the correct size. while( nBuffSize >= m_ulDeviceBufferSize ) nBuffSize = nBuffSize>>1; //Now make sure we never break a buffer apart in the //middle of a Frame. int nRem = nBuffSize%(m_uSampFrameSize*m_unNumChannels); nBuffSize = nBuffSize>nRem ? nBuffSize-nRem : nBuffSize ; //March through the buffer, breaking it up. UCHAR* pData = pAudioOutHdr->pData->GetBuffer(); int nLimit = pAudioOutHdr->pData->GetSize(); int nPtr = 0; while( nPtr < nLimit ) { IHXBuffer* pNewBuffer = new CHXBuffer(); if( nPtr+nBuffSize <= nLimit ) { pNewBuffer->Set( pData+nPtr, nBuffSize ); } else { pNewBuffer->Set( pData+nPtr, (nLimit-nPtr) ); } m_pWriteList->AddTail( pNewBuffer ); pNewBuffer->AddRef(); nPtr += nBuffSize; } } UNLOCK(m_mtxWriteListPlayStateLock); } #if defined(_THREADED_AUDIO) && defined(_UNIX_THREADS_SUPPORTED) //If we are using threaded audio just return and let the audio thread //grab the data and write it to the device. if( m_bUserWantsThreads ) { return RA_AOE_NOERR; } #endif // ----------- Threaded audio note: // // We don't need to protect anything below here with the mutex // because we will never get here if we are running with threaded // audio. // //XXXgfw This doesn't seem to be doing any good anymore. So I am taking it // out // //Check for underflow here and do what? Pause it? // if( m_wState!=RA_AOS_OPEN_PAUSED && m_pWriteList->GetCount() <= 0 ) // { // ULONG32 ulTmp = 0; // _GetRoomOnDevice(ulTmp); // //there better be stuff in the device because we wouldn't get this // //far if we were not in an OPEN/Playing state. // if( ulTmp >= m_ulDeviceBufferSize ) // { // //XXXgfw, read below... // //We aren't writting anything and there is no // //data in the device buffer. Lions, tigers and bears.. // //Maybe we should just do a _Pause here to keep // //the byte count from the audio device correct? // HX_ASSERT( "UNDERFLOW in AUDIO DEVICE" == NULL ); // } // } //Just return if there is nothing to do if( m_pWriteList->GetCount() <= 0 || m_wState==RA_AOS_OPEN_PAUSED ) { return RA_AOE_NOERR; } // // We only want to write to the device if we can send the // whole buffer. If there isn't that much room then just // put the buffer back in the front of the que. // ULONG32 ulBytesAvailable = 0; HX_RESULT code = RA_AOE_NOERR; code = _GetRoomOnDevice(ulBytesAvailable); if( RA_AOE_NOERR != code ) { //What? Can't get the room on the device for some reason. m_wLastError = code; return m_wLastError; } //Peek at the head. pBuffer = (IHXBuffer*)m_pWriteList->GetHead(); if( NULL==pBuffer || ulBytesAvailable < pBuffer->GetSize()) { m_wLastError = RA_AOE_NOERR; return m_wLastError; } ulCount = _PushBits(); if ( m_bFirstWrite ) { m_bFirstWrite = FALSE; /* Initialize the playback callback time. */ HXTimeval lTime = m_pScheduler->GetCurrentSchedulerTime(); m_pPlaybackCountCBTime->tv_sec = lTime.tv_sec; m_pPlaybackCountCBTime->tv_usec = lTime.tv_usec; /* Scheduler playback callback. */ ReschedPlaybackCheck(); } //make sure the device is kept full. if( m_pWriteList->GetCount()>0 && ulCount>0 ) _Imp_Write(NULL); return m_wLastError; } HX_RESULT CAudioOutUNIX::_CheckFormat( const HXAudioFormat* pFormat ) { return RA_AOE_NOERR; }