www.pudn.com > helix.src.0812.rar > audbeos.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 ***** */ /******************************************************************* * audbeos.cpp * * CLASS: CAudioOutBeOS * * DESCRIPTION: Class implementation for BeOS-specific audio devices * *******************************************************************/ #include "hxtypes.h" #include "hxresult.h" #include "hxcom.h" #include "ihxpckts.h" #include "hxausvc.h" #include "auderrs.h" #include "hxaudev.h" #include "hxtick.h" #include "hxthread.h" #include "hxbuffer.h" #include "hxslist.h" #include "hxengin.h" #include "timeval.h" #include "hxaudses.h" #include "audbeos.h" #include#include struct IHXCallback; // forward declaration needed for callback. CAudioOutBeOS::CAudioOutBeOS() : m_player(NULL) , m_bFirstWrite(TRUE) , m_pPendingBufferList(NULL) , m_ulLastPlayCursor(0) , m_ulCurrentPlayTime(0) , m_ulLastTimeSync(0) , m_bPlaying(FALSE) , m_ulBufferSize(0) , m_ulFrameSize(0) , m_ulNextWriteOffset(0) , m_ulOldBytesLeft(0) , m_ulBlockSize(0) , m_bGotAWrite(FALSE) #if _BEOS_AUDIODEV_CALLBACK , m_pPlaybackCountCBTime(NULL) , m_bCallbackPending(FALSE) , m_PendingCallbackID(0) #endif { #if _BEOS_AUDIODEV_CALLBACK m_pPlaybackCountCBTime = new Timeval; #endif } CAudioOutBeOS::~CAudioOutBeOS() { _Imp_Close(); #if _BEOS_AUDIODEV_CALLBACK delete m_pPlaybackCountCBTime; #endif } HX_RESULT CAudioOutBeOS::_Imp_Open(const HXAudioFormat* pFormat) { size_t bufSize; void *basePtr; SetFormat(pFormat); #if _BEOS_AUDIODEV_CALLBACK if (m_pOwner) { m_pOwner->GetScheduler(&m_pScheduler); m_pScheduler->AddRef(); } #endif // set up the player with enough buffer space for about 4 seconds of audio // m_player = new BPushGameSound(m_gameSoundFormat.frame_rate, &m_gameSoundFormat, 4); // BPushGameSound->CurrentPosition is accurate to within a buffer // Therefore, to keep good synchronization between audio and video, a single // buffer should not have a longer duration than a frame of video. We don't // know here what the video framerate (if any) may be, but we can assume that it // isn't over 30fps. Thus, each audio buffer should be no longer than about 30 ms. // To store about 3 seconds of audio, we'll need 100 buffers. bufSize = (size_t)(m_gameSoundFormat.frame_rate * 0.03f); m_player = new BPushGameSound(bufSize, &m_gameSoundFormat, 100); if (m_player->InitCheck() != B_OK) { delete m_player; m_player = NULL; return RA_AOE_BADOPEN; } if (m_player->LockForCyclic(&basePtr, &bufSize) != BPushGameSound::lock_ok) m_player->UnlockCyclic(); m_ulBufferSize = bufSize; m_ulBlockSize = m_ulBytesPerGran; return HXR_OK; } HX_RESULT CAudioOutBeOS::_Imp_Close() { #if _BEOS_AUDIODEV_CALLBACK if (m_pScheduler) { // Remove callback from scheduler if (m_bCallbackPending) { m_pScheduler->Remove(m_PendingCallbackID); m_bCallbackPending = FALSE; } m_pScheduler->Release(); m_pScheduler = NULL; } #endif if (m_pPendingBufferList) { while (!m_pPendingBufferList->IsEmpty()) { IHXBuffer *pBuffer = (IHXBuffer *)m_pPendingBufferList->RemoveHead(); pBuffer->Release(); pBuffer = NULL; } delete m_pPendingBufferList; m_pPendingBufferList = NULL; } if (m_player) { m_player->StopPlaying(); delete m_player; m_player = NULL; } m_bPlaying = FALSE; return HXR_OK; } HX_RESULT CAudioOutBeOS::_Imp_Seek(ULONG32 ulSeekTime) { return HXR_OK; } HX_RESULT CAudioOutBeOS::_Imp_Pause() { if (m_player) { size_t curPos = m_player->CurrentPosition() * m_ulFrameSize; uint8 *pAudioPtr; size_t i, ulAudioBytes; m_player->StopPlaying(); if (m_player->LockForCyclic((void **)&pAudioPtr, &ulAudioBytes) == BPushGameSound::lock_ok && pAudioPtr) { // shift the audio data all back to the beginning of the buffer if (m_ulNextWriteOffset > curPos) { for (i = curPos; i < m_ulNextWriteOffset; i++) { pAudioPtr[i - curPos] = pAudioPtr[i]; } m_ulNextWriteOffset = m_ulNextWriteOffset - curPos; } else { // wrap-around uint32 remaining = ulAudioBytes - curPos; uint8 *tempBuf = new uint8[m_ulNextWriteOffset]; for (i = 0; i < m_ulNextWriteOffset; i++) { tempBuf[i] = pAudioPtr[i]; } for (i = curPos; i < ulAudioBytes; i++) { pAudioPtr[i - curPos] = pAudioPtr[i]; } for (i = 0; i < m_ulNextWriteOffset; i++) { pAudioPtr[i + remaining] = tempBuf[i]; } delete [] tempBuf; m_ulNextWriteOffset = remaining + m_ulNextWriteOffset; } m_player->UnlockCyclic(); m_bGotAWrite = TRUE; m_ulLastPlayCursor = 0; } } m_bPlaying = FALSE; return HXR_OK; } HX_RESULT CAudioOutBeOS::_Imp_Resume() { if (m_player) m_player->StartPlaying(); m_ulLastPlayCursor = 0; m_bPlaying = TRUE; m_ulLastTimeSync = HX_GET_TICKCOUNT(); OnTimeSync(); return HXR_OK; } HX_RESULT CAudioOutBeOS::_Imp_Write(const HXAudioData* pAudioOutData) { HX_RESULT theErr = HXR_OK; IHXBuffer *pBuffer = NULL; #if _BEOS_AUDIODEV_CALLBACK if (m_bFirstWrite && pAudioOutData) { /* Initialize the playback callback time. */ HXTimeval lTime = m_pScheduler->GetCurrentSchedulerTime(); m_pPlaybackCountCBTime->tv_sec = lTime.tv_sec; m_pPlaybackCountCBTime->tv_usec = lTime.tv_usec; m_bFirstWrite = FALSE; ReschedPlaybackCheck(); } #endif if (pAudioOutData && pAudioOutData->pData) { pAudioOutData->pData->AddRef(); } if (m_pPendingBufferList && m_pPendingBufferList->GetCount() > 0) { if (pAudioOutData && pAudioOutData->pData) m_pPendingBufferList->AddTail(pAudioOutData->pData); pBuffer = (IHXBuffer *)m_pPendingBufferList->RemoveHead(); } else { if (pAudioOutData && pAudioOutData->pData) pBuffer = pAudioOutData->pData; } BOOL bCanContinue = TRUE; while (bCanContinue && pBuffer) { UINT32 ulBufferSize = pBuffer->GetSize(); uint8* pAudioPtr1 = NULL; uint8* pAudioPtr2 = NULL; size_t curPos; size_t ulAudioBytes1 = 0; size_t ulAudioBytes2 = 0; size_t ulAudioBytesWritten1 = 0; size_t ulAudioBytesWritten2 = 0; if (m_player->LockForCyclic((void **)&pAudioPtr1, &ulAudioBytes1) == BPushGameSound::lock_ok && pAudioPtr1) { size_t ulSizeToWrite, curPos; if (m_player->IsPlaying()) curPos = m_player->CurrentPosition() * m_ulFrameSize; else curPos = 0; if (m_ulNextWriteOffset >= curPos) { pAudioPtr2 = pAudioPtr1; ulAudioBytes1 -= m_ulNextWriteOffset; } else { pAudioPtr2 = NULL; ulAudioBytes1 = curPos - m_ulNextWriteOffset - 1; } pAudioPtr1 += m_ulNextWriteOffset; if (ulAudioBytes1 < 0) ulAudioBytes1 = 0; ulSizeToWrite = ulBufferSize; if (ulSizeToWrite > ulAudioBytes1) { ulSizeToWrite = ulAudioBytes1; } if (ulSizeToWrite > 0) { ::memcpy(pAudioPtr1, (void*) pBuffer->GetBuffer(), ulSizeToWrite); /* Flawfinder: ignore */ } ulAudioBytesWritten1 = ulSizeToWrite; size_t ulRemainingToWrite = ulBufferSize - ulSizeToWrite; ulAudioBytes2 = curPos - 1; if (ulRemainingToWrite > 0 && pAudioPtr2 && ulAudioBytes2 > 0) { ulSizeToWrite = ulRemainingToWrite; if (ulSizeToWrite > ulAudioBytes2) { ulSizeToWrite = ulAudioBytes2; } ::memcpy(pAudioPtr2, (void*) (pBuffer->GetBuffer() + ulAudioBytesWritten1), ulSizeToWrite); ulAudioBytesWritten2 = ulSizeToWrite; ulRemainingToWrite -= ulSizeToWrite; } if (ulRemainingToWrite > 0) { IHXBuffer* pNewBuffer = (IHXBuffer*) new CHXBuffer; pNewBuffer->AddRef(); pNewBuffer->Set(pBuffer->GetBuffer() + (ulBufferSize - ulRemainingToWrite), ulRemainingToWrite); if (!m_pPendingBufferList) { m_pPendingBufferList = new CHXSimpleList; } m_pPendingBufferList->AddHead((void*) pNewBuffer); // no more space in the secondary buffer bCanContinue = FALSE; } m_player->UnlockCyclic(); m_ulNextWriteOffset += ulAudioBytesWritten1 + ulAudioBytesWritten2; if (pAudioOutData) m_bGotAWrite = TRUE; if (m_ulNextWriteOffset >= m_ulBufferSize) { m_ulNextWriteOffset -= m_ulBufferSize; } if (m_bFirstWrite) { m_bFirstWrite = FALSE; } } // lock else { bCanContinue = FALSE; if (!m_pPendingBufferList) { m_pPendingBufferList = new CHXSimpleList; } pBuffer->AddRef(); m_pPendingBufferList->AddHead((void*) pBuffer); } pBuffer->Release(); pBuffer = NULL; if (bCanContinue && m_pPendingBufferList && m_pPendingBufferList->GetCount() > 0) { pBuffer = (IHXBuffer*) m_pPendingBufferList->RemoveHead(); } } // while if (m_bPlaying) { UINT32 ulCurrentTime = HX_GET_TICKCOUNT(); if (CALCULATE_ELAPSED_TICKS(m_ulLastTimeSync, ulCurrentTime) > 100) { m_ulLastTimeSync = ulCurrentTime; OnTimeSync(); } } //@ This is an ugly hack, but it seems to work very well. // Hopefully I can figure it out and fix it properly at some point. // (mclifton 10/5/99) /*if (pAudioOutData && m_bPlaying && m_pPendingBufferList && m_pPendingBufferList->GetCount() > 0) { snooze(50000); _Imp_Write(NULL); } if (pAudioOutData && m_bPlaying && m_pPendingBufferList && m_pPendingBufferList->GetCount() > 0) { snooze(50000); _Imp_Write(NULL); }*/ return theErr; } HX_RESULT CAudioOutBeOS::_Imp_Reset() { m_ulCurrentPlayTime = 0; m_bFirstWrite = TRUE; m_ulNextWriteOffset = 0; if (m_player) m_player->StopPlaying(); m_bPlaying = FALSE; return HXR_OK; } HX_RESULT CAudioOutBeOS::_Imp_Drain() { return HXR_OK; } BOOL CAudioOutBeOS::_Imp_SupportsVolume() { return TRUE; } UINT16 CAudioOutBeOS::_Imp_GetVolume() { float vol = 0.0f; if (m_player) vol = m_player->Gain(); return (UINT16)(vol * 100.0f); } HX_RESULT CAudioOutBeOS::_Imp_SetVolume(const UINT16 uVolume) { float vol = (float)uVolume / 100.0f; if (m_player) m_player->SetGain(vol); return HXR_OK; } HX_RESULT CAudioOutBeOS::_Imp_CheckFormat(const HXAudioFormat* pFormat) { return HXR_OK; } HX_RESULT CAudioOutBeOS::_Imp_GetCurrentTime(ULONG32& ulCurrentTime) { size_t curPos = 0; if (m_player) { if (m_player->IsPlaying()) curPos = m_player->CurrentPosition() * m_ulFrameSize; // This method of calculating elapsed time was basically copied over from // the DirectSound code. The bad news is that it is error-prone. Converting // from bytes to milliseconds is prone to roundoff, leading to an accumulation // of error, causing a drift of synchronization between audio and video. // (That DirectSound code has caused me nothing but headaches...) //m_ulCurrentPlayTime += // CalcMs(CalculateElapsedBytes(m_ulLastPlayCursor, curPos)); // The more accurate way to do it is to accumulate elapsed bytes, then // convert the total to milliseconds. The elapsed bytes is accurate to // within a video frame or so. Even though it can't be absolutely accurate, // at least it won't drift over time. m_ulCurrentPlayTime += CalculateElapsedBytes(m_ulLastPlayCursor, curPos); m_ulLastPlayCursor = curPos; } // old method //ulCurrentTime = m_ulCurrentPlayTime; // new method ulCurrentTime = CalcMs(m_ulCurrentPlayTime); return HXR_OK; } UINT16 CAudioOutBeOS::_NumberOfBlocksRemainingToPlay(void) { UINT32 res = 0; size_t curPos = 0; // add up the number of audio bytes queued up if (m_pPendingBufferList) { LISTPOSITION i = m_pPendingBufferList->GetHeadPosition(); while (i) { res += ((IHXBuffer *)m_pPendingBufferList->GetAt(i))->GetSize(); m_pPendingBufferList->GetNext(i); } } // add in the bytes that are currently in the playback buffer if (m_player) { UINT32 playingBytes = 0; if (m_player->IsPlaying()) curPos = m_player->CurrentPosition() * m_ulFrameSize; if (curPos < m_ulNextWriteOffset) playingBytes += m_ulNextWriteOffset - curPos; else playingBytes += (m_ulBufferSize - curPos) + m_ulNextWriteOffset; res += playingBytes; } if (m_bGotAWrite) m_ulOldBytesLeft = res; else if (res > m_ulOldBytesLeft) { fprintf(stderr, "Buffer overflow!\n"); // This is a bad situation - I wish this never happened. // But what should I do when it does happen? // I used to return 0, since I thought that would force more // audio buffers my way, but it also seems to introduce more glitches. //res = 0; } m_bGotAWrite = FALSE; res /= m_ulBlockSize; return res; } void CAudioOutBeOS::SetFormat(const HXAudioFormat* pFormat) { ::memset(&m_gameSoundFormat, 0, sizeof(gs_audio_format)); m_gameSoundFormat.frame_rate = pFormat->ulSamplesPerSec; m_gameSoundFormat.channel_count = pFormat->uChannels; switch (pFormat->uBitsPerSample) { case 8: m_gameSoundFormat.format = gs_audio_format::B_GS_U8; break; case 16: m_gameSoundFormat.format = gs_audio_format::B_GS_S16; break; } m_gameSoundFormat.byte_order = B_MEDIA_LITTLE_ENDIAN; m_gameSoundFormat.buffer_size = 2048; m_ulFrameSize = m_gameSoundFormat.channel_count * ((m_gameSoundFormat.format==gs_audio_format::B_GS_U8)?1:2); } inline UINT32 CAudioOutBeOS::CalcMs(ULONG32 ulNumBytes) { return ( (ULONG32) (( 1000.0 / (m_ulFrameSize * m_gameSoundFormat.frame_rate) ) * ulNumBytes) ); } inline UINT32 CAudioOutBeOS::CalculateElapsedBytes(UINT32 ulLastBytePos, UINT32 ulCurrentBytePos) { return ((ulCurrentBytePos >= ulLastBytePos) ? (ulCurrentBytePos - ulLastBytePos) : (ulCurrentBytePos + (m_ulBufferSize - ulLastBytePos))); } #if _BEOS_AUDIODEV_CALLBACK void CAudioOutBeOS::DoTimeSyncs(void) { ReschedPlaybackCheck(); OnTimeSync(); return; } HX_RESULT CAudioOutBeOS::ReschedPlaybackCheck() { HX_RESULT theErr = HXR_OK; if (m_bCallbackPending) return theErr; *m_pPlaybackCountCBTime += (int)(m_ulGranularity * 1000) / 2; // Put this back in the scheduler. HXPlaybackCountCb *pCallback = 0; pCallback = new HXPlaybackCountCb(TRUE); if (pCallback) { pCallback->m_pAudioDeviceObject = this; m_bCallbackPending = TRUE; m_PendingCallbackID = m_pScheduler->AbsoluteEnter(pCallback, *((HXTimeval *)m_pPlaybackCountCBTime)); } else theErr = HXR_OUTOFMEMORY; // but ignored, why? return theErr; } CAudioOutBeOS::HXPlaybackCountCb::HXPlaybackCountCb(BOOL timed) : m_lRefCount(0) , m_pAudioDeviceObject(0) , m_timed(timed) { } CAudioOutBeOS::HXPlaybackCountCb::~HXPlaybackCountCb() { } /* * IUnknown methods */ ///////////////////////////////////////////////////////////////////////// // Method: // IUnknown::QueryInterface // Purpose: // Implement this to export the interfaces supported by your // object. // STDMETHODIMP CAudioOutBeOS::HXPlaybackCountCb::QueryInterface(REFIID riid, void** ppvObj) { if (IsEqualIID(riid, IID_IHXCallback)) { AddRef(); *ppvObj = (IHXCallback*)this; return HXR_OK; } else if (IsEqualIID(riid, IID_IUnknown)) { AddRef(); *ppvObj = this; return HXR_OK; } *ppvObj = NULL; return HXR_NOINTERFACE; } ///////////////////////////////////////////////////////////////////////// // Method: // IUnknown::AddRef // Purpose: // Everyone usually implements this the same... feel free to use // this implementation. // STDMETHODIMP_(ULONG32) CAudioOutBeOS::HXPlaybackCountCb::AddRef() { return InterlockedIncrement(&m_lRefCount); } ///////////////////////////////////////////////////////////////////////// // Method: // IUnknown::Release // Purpose: // Everyone usually implements this the same... feel free to use // this implementation. // STDMETHODIMP_(ULONG32) CAudioOutBeOS::HXPlaybackCountCb::Release() { if (InterlockedDecrement(&m_lRefCount) > 0) { return m_lRefCount; } delete this; return 0; } /* * IHXPlaybackCountCb methods */ STDMETHODIMP CAudioOutBeOS::HXPlaybackCountCb::Func(void) { if (m_pAudioDeviceObject) { if (!m_timed) { m_pAudioDeviceObject->_Imp_Write(NULL); } else { m_pAudioDeviceObject->_Imp_Write(NULL); m_pAudioDeviceObject->m_bCallbackPending = FALSE; m_pAudioDeviceObject->DoTimeSyncs(); } } return HXR_OK; } #endif