rendered paste body/*
* XBMC Media Center
* Copyright (c) 2002 d7o3g4q and RUNTiME
* Portions Copyright (c) by the authors of ffmpeg and xvid
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "stdafx.h"
#include "Win32DirectSound.h"
#include "AudioContext.h"
#include "Settings.h"
#include <initguid.h>
#include <Mmreg.h>
#pragma comment(lib, "dxguid.lib")
DEFINE_GUID( _KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, WAVE_FORMAT_IEEE_FLOAT, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 );
DEFINE_GUID( _KSDATAFORMAT_SUBTYPE_PCM, WAVE_FORMAT_PCM, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 );
DEFINE_GUID( _KSDATAFORMAT_SUBTYPE_DOLBY_AC3_SPDIF, WAVE_FORMAT_DOLBY_AC3_SPDIF, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 );
const int dsound_channel_mask[] =
{
SPEAKER_FRONT_CENTER,
SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT,
SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_LOW_FREQUENCY,
SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT,
SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | SPEAKER_LOW_FREQUENCY,
SPEAKER_FRONT_LEFT | SPEAKER_FRONT_CENTER | SPEAKER_FRONT_RIGHT | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | SPEAKER_LOW_FREQUENCY
};
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
//***********************************************************************************************
CWin32DirectSound::CWin32DirectSound() :
m_Passthrough(false),
m_AvgBytesPerSec(0),
m_CacheLen(0),
m_dwChunkSize(0),
m_dwBufferLen(0),
m_PreCacheSize(0),
m_LastCacheCheck(0),
m_pChannelMap(NULL)
{
}
bool CWin32DirectSound::Initialize(IAudioCallback* pCallback, int iChannels, unsigned int uiSamplesPerSec, unsigned int uiBitsPerSample, bool bResample, const char* strAudioCodec, bool bIsMusic, bool bAudioPassthrough)
{
bool bAudioOnAllSpeakers(false);
g_audioContext.SetupSpeakerConfig(iChannels, bAudioOnAllSpeakers, bIsMusic);
if(bAudioPassthrough)
g_audioContext.SetActiveDevice(CAudioContext::DIRECTSOUND_DEVICE_DIGITAL);
else
g_audioContext.SetActiveDevice(CAudioContext::DIRECTSOUND_DEVICE);
m_pDSound=g_audioContext.GetDirectSoundDevice();
m_bPause = false;
m_bIsAllocated = false;
m_pBuffer = NULL;
m_uiChannels = iChannels;
m_uiSamplesPerSec = uiSamplesPerSec;
m_uiBitsPerSample = uiBitsPerSample;
m_Passthrough = bAudioPassthrough;
m_nCurrentVolume = g_stSettings.m_nVolumeLevel;
WAVEFORMATEXTENSIBLE wfxex = {0};
//fill waveformatex
ZeroMemory(&wfxex, sizeof(WAVEFORMATEXTENSIBLE));
wfxex.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE)-sizeof(WAVEFORMATEX);
wfxex.Format.nChannels = iChannels;
wfxex.Format.nSamplesPerSec = uiSamplesPerSec;
if (bAudioPassthrough == true)
{
wfxex.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
wfxex.Format.wFormatTag = WAVE_FORMAT_DOLBY_AC3_SPDIF;
wfxex.SubFormat = _KSDATAFORMAT_SUBTYPE_DOLBY_AC3_SPDIF;
wfxex.Format.wBitsPerSample = 16;
wfxex.Format.nChannels = 2;
}
else
{
if (iChannels > 2)
wfxex.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
else
wfxex.Format.wFormatTag = WAVE_FORMAT_PCM;
wfxex.SubFormat = _KSDATAFORMAT_SUBTYPE_PCM;
wfxex.Format.wBitsPerSample = uiBitsPerSample;
}
wfxex.Samples.wValidBitsPerSample = wfxex.Format.wBitsPerSample;
wfxex.Format.nBlockAlign = wfxex.Format.nChannels * (wfxex.Format.wBitsPerSample >> 3);
wfxex.Format.nAvgBytesPerSec = wfxex.Format.nSamplesPerSec * wfxex.Format.nBlockAlign;
wfxex.dwChannelMask = dsound_channel_mask[iChannels - 1];
m_AvgBytesPerSec = wfxex.Format.nAvgBytesPerSec;
// unsure if these are the right values
m_dwChunkSize = wfxex.Format.nBlockAlign * 3096;
m_dwBufferLen = m_dwChunkSize * 16;
CLog::Log(LOGDEBUG, __FUNCTION__": Packet Size = %d. Avg Bytes Per Second = %d.", m_dwChunkSize, m_AvgBytesPerSec);
// fill in the secondary sound buffer descriptor
DSBUFFERDESC dsbdesc;
memset(&dsbdesc, 0, sizeof(DSBUFFERDESC));
dsbdesc.dwSize = sizeof(DSBUFFERDESC);
dsbdesc.dwFlags = DSBCAPS_GETCURRENTPOSITION2 /** Better position accuracy */
| DSBCAPS_GLOBALFOCUS /** Allows background playing */
| DSBCAPS_CTRLVOLUME /** volume control enabled */
| DSBCAPS_LOCHARDWARE; /** Needed for 5.1 on emu101k */
dsbdesc.dwBufferBytes = m_dwBufferLen;
dsbdesc.lpwfxFormat = (WAVEFORMATEX *)&wfxex;
// now create the stream buffer
HRESULT res = IDirectSound_CreateSoundBuffer(m_pDSound, &dsbdesc, &m_pBuffer, NULL);
if (res != DS_OK)
{
if (dsbdesc.dwFlags & DSBCAPS_LOCHARDWARE) // DSBCAPS_LOCHARDWARE Always fails on Vista, by design
{
SAFE_RELEASE(m_pBuffer);
CLog::Log(LOGDEBUG, __FUNCTION__": Couldn't create secondary buffer (%s). Trying without LOCHARDWARE.", dserr2str(res));
// Try without DSBCAPS_LOCHARDWARE
dsbdesc.dwFlags &= ~DSBCAPS_LOCHARDWARE;
res = IDirectSound_CreateSoundBuffer(m_pDSound, &dsbdesc, &m_pBuffer, NULL);
}
if (res != DS_OK && dsbdesc.dwFlags & DSBCAPS_CTRLVOLUME)
{
SAFE_RELEASE(m_pBuffer);
CLog::Log(LOGDEBUG, __FUNCTION__": Couldn't create secondary buffer (%s). Trying without CTRLVOLUME.", dserr2str(res));
// Try without DSBCAPS_CTRLVOLUME
dsbdesc.dwFlags &= ~DSBCAPS_CTRLVOLUME;
res = IDirectSound_CreateSoundBuffer(m_pDSound, &dsbdesc, &m_pBuffer, NULL);
}
if (res != DS_OK)
{
SAFE_RELEASE(m_pBuffer);
CLog::Log(LOGERROR, __FUNCTION__": cannot create secondary buffer (%s)", dserr2str(res));
return false;
}
}
CLog::Log(LOGDEBUG, __FUNCTION__": secondary buffer created");
// Set up channel mapping
m_pChannelMap = GetChannelMap(iChannels, strAudioCodec);
m_pBuffer->Stop();
if (DSERR_CONTROLUNAVAIL == m_pBuffer->SetVolume(g_stSettings.m_nVolumeLevel))
CLog::Log(LOGINFO, __FUNCTION__": Volume control is unavailable in the current configuration");
m_bIsAllocated = true;
m_BufferOffset = 0;
m_CacheLen = 0;
m_LastCacheCheck = timeGetTime();
return m_bIsAllocated;
}
//***********************************************************************************************
CWin32DirectSound::~CWin32DirectSound()
{
Deinitialize();
}
//***********************************************************************************************
bool CWin32DirectSound::Deinitialize()
{
if (m_bIsAllocated)
{
CLog::Log(LOGDEBUG, __FUNCTION__": Cleaning up");
m_bIsAllocated = false;
if (m_pBuffer)
{
m_pBuffer->Stop();
SAFE_RELEASE(m_pBuffer);
}
m_pBuffer = NULL;
m_pDSound = NULL;
m_BufferOffset = 0;
m_CacheLen = 0;
m_dwChunkSize = 0;
m_dwBufferLen = 0;
g_audioContext.SetActiveDevice(CAudioContext::DEFAULT_DEVICE);
}
return true;
}
//***********************************************************************************************
bool CWin32DirectSound::Pause()
{
CSingleLock lock (m_critSection);
if (m_bPause) // Already paused
return true;
m_bPause = true;
m_pBuffer->Stop();
return true;
}
//***********************************************************************************************
bool CWin32DirectSound::Resume()
{
CSingleLock lock (m_critSection);
if (!m_bPause) // Already playing
return true;
m_bPause = false;
if (m_CacheLen > m_PreCacheSize) // Make sure we have some data to play (if not, playback will start when we add some)
m_pBuffer->Play(0, 0, DSBPLAY_LOOPING);
return true;
}
//***********************************************************************************************
bool CWin32DirectSound::Stop()
{
CSingleLock lock (m_critSection);
// Stop and reset DirectSound buffer
m_pBuffer->Stop();
m_pBuffer->SetCurrentPosition(0);
// Reset buffer management members
m_BufferOffset = 0;
m_CacheLen = 0;
m_bPause = false;
return true;
}
//***********************************************************************************************
long CWin32DirectSound::GetMinimumVolume() const
{
return DSBVOLUME_MIN;
}
//***********************************************************************************************
long CWin32DirectSound::GetMaximumVolume() const
{
return DSBVOLUME_MAX;
}
//***********************************************************************************************
long CWin32DirectSound::GetCurrentVolume() const
{
return m_nCurrentVolume;
}
//***********************************************************************************************
void CWin32DirectSound::Mute(bool bMute)
{
CSingleLock lock (m_critSection);
if (!m_bIsAllocated) return;
if (bMute)
m_pBuffer->SetVolume(GetMinimumVolume());
else
m_pBuffer->SetVolume(m_nCurrentVolume);
}
//***********************************************************************************************
bool CWin32DirectSound::SetCurrentVolume(long nVolume)
{
CSingleLock lock (m_critSection);
if (!m_bIsAllocated) return -1;
m_nCurrentVolume = nVolume;
return m_pBuffer->SetVolume( m_nCurrentVolume );
}
//***********************************************************************************************
unsigned int CWin32DirectSound::AddPackets(const void* data, unsigned int len)
{
CSingleLock lock (m_critSection);
DWORD total = len;
unsigned char* pBuffer = (unsigned char*)data;
while (len >= m_dwChunkSize && GetSpace() >= m_dwChunkSize) // We want to write at least one chunk at a time
{
LPVOID start = NULL, startWrap = NULL;
DWORD size = 0, sizeWrap = 0;
if (m_BufferOffset >= m_dwBufferLen) // Wrap-around manually
m_BufferOffset = 0;
if (FAILED(m_pBuffer->Lock(m_BufferOffset, m_dwChunkSize, &start, &size, &startWrap, &sizeWrap, 0)))
{
CLog::Log(LOGERROR, __FUNCTION__ ": Unable to lock buffer at offset %u.", m_BufferOffset);
break;
}
// Write data into the buffer
MapDataIntoBuffer(pBuffer, size, (unsigned char*)start);
m_BufferOffset += size;
if (startWrap) // Write-region wraps to beginning of buffer
{
MapDataIntoBuffer(pBuffer + size, sizeWrap, (unsigned char*)startWrap);
m_BufferOffset = sizeWrap;
}
size_t bytes = size + sizeWrap;
m_CacheLen += bytes; // This data is now in the cache
pBuffer += bytes; // Update buffer pointer
len -= bytes; // Update remaining data len
m_pBuffer->Unlock(start, size, startWrap, sizeWrap);
}
CheckPlayStatus();
return total - len; // Bytes used
}
void CWin32DirectSound::UpdateCacheStatus()
{
CSingleLock lock (m_critSection);
// TODO: Check to see if we may have cycled around since last time
unsigned int time = timeGetTime();
if (time == m_LastCacheCheck)
return; // Don't recalc more frequently than once/ms (that is our max resolution anyway)
DWORD playCursor = 0, writeCursor = 0;
if (FAILED(m_pBuffer->GetCurrentPosition(&playCursor, &writeCursor))) // Get the current playback and safe write positions
{
CLog::Log(LOGERROR,__FUNCTION__ ": GetCurrentPosition failed. Unable to determine buffer status");
return;
}
m_LastCacheCheck = time;
// Check the state of the ring buffer (P->O->W == underrun)
// These are the logical situations that can occur
// O: CurrentOffset W: WriteCursor P: PlayCursor
// | | | | | | | | | |
// ***O----W----P***** < underrun P > W && O < W (1)
// | | | | | | | | | |
// ---P****O----W----- < underrun O > P && O < W (2)
// | | | | | | | | | |
// ---W----P****O----- < underrun P > W && P < O (3)
// | | | | | | | | | |
// ***W****O----P***** P > W && P > O (4)
// | | | | | | | | | |
// ---P****W****O----- P < W && O > W (5)
// | | | | | | | | | |
// ***O----P****W***** P < W && O < P (6)
// Check for underruns
if ((playCursor > writeCursor && m_BufferOffset < writeCursor) || // (1)
(playCursor < m_BufferOffset && m_BufferOffset < writeCursor) || // (2)
(playCursor > writeCursor && playCursor < m_BufferOffset)) // (3)
{
CLog::Log(LOGWARNING, "CWin32DirectSound::GetSpace - buffer underrun - W:%u, P:%u, O:%u.", writeCursor, playCursor, m_BufferOffset);
m_BufferOffset = writeCursor; // Catch up
m_pBuffer->Stop(); // Wait until someone gives us some data to restart playback (prevents glitches)
}
// Calculate available space in the ring buffer
if (playCursor == m_BufferOffset && m_BufferOffset == writeCursor) // Playback is stopped and we are all at the same place
m_CacheLen = 0;
else if (m_BufferOffset > playCursor)
m_CacheLen = m_BufferOffset - playCursor;
else
m_CacheLen = m_dwBufferLen - (playCursor - m_BufferOffset);
}
void CWin32DirectSound::CheckPlayStatus()
{
unsigned int status = 0;
m_pBuffer->GetStatus(&status);
if(!m_bPause && !(status & DSBSTATUS_PLAYING) && m_CacheLen > m_PreCacheSize) // If we have some data, see if we can start playback
{
m_pBuffer->Play(0, 0, DSBPLAY_LOOPING);
CLog::Log(LOGDEBUG,__FUNCTION__ ": Resuming Playback");
}
}
unsigned int CWin32DirectSound::GetSpace()
{
CSingleLock lock (m_critSection);
UpdateCacheStatus();
return m_dwBufferLen - m_CacheLen;
}
//***********************************************************************************************
float CWin32DirectSound::GetDelay()
{
// Make sure we know how much data is in the cache
UpdateCacheStatus();
CSingleLock lock (m_critSection);
float delay = 0.008f; // WTF?
delay += (float)m_CacheLen / (float)m_AvgBytesPerSec;
return delay;
}
//***********************************************************************************************
float CWin32DirectSound::GetCacheTime()
{
CSingleLock lock (m_critSection);
// Make sure we know how much data is in the cache
UpdateCacheStatus();
return (float)m_CacheLen / (float)m_AvgBytesPerSec;
}
//***********************************************************************************************
unsigned int CWin32DirectSound::GetChunkLen()
{
return m_dwChunkSize;
}
//***********************************************************************************************
int CWin32DirectSound::SetPlaySpeed(int iSpeed)
{
return 0;
}
//***********************************************************************************************
void CWin32DirectSound::RegisterAudioCallback(IAudioCallback *pCallback)
{
m_pCallback = pCallback;
}
//***********************************************************************************************
void CWin32DirectSound::UnRegisterAudioCallback()
{
m_pCallback = NULL;
}
//***********************************************************************************************
void CWin32DirectSound::WaitCompletion()
{
CSingleLock lock (m_critSection);
DWORD status, timeout;
unsigned char* silence;
if (!m_pBuffer)
return ;
if(FAILED(m_pBuffer->GetStatus(&status)) || (status & DSBSTATUS_PLAYING) == 0)
return; // We weren't playing anyway
// The drain should complete in the time occupied by the cache
timeout = (DWORD)(1000 * GetDelay());
timeout += timeGetTime();
silence = (unsigned char*)calloc(1,m_dwChunkSize); // Initialize 'silence' to zero...
while(AddPackets(silence, m_dwChunkSize) == 0)
{
if(FAILED(m_pBuffer->GetStatus(&status)) || (status & DSBSTATUS_PLAYING) == 0)
break;
if(timeout < timeGetTime())
{
CLog::Log(LOGWARNING, __FUNCTION__ ": timeout adding silence to buffer");
break;
}
}
free(silence);
while(m_CacheLen)
{
if(FAILED(m_pBuffer->GetStatus(&status)) || (status & DSBSTATUS_PLAYING) == 0)
break;
if(timeout < timeGetTime())
{
CLog::Log(LOGDEBUG, "CWin32DirectSound::WaitCompletion - timeout waiting for silence");
break;
}
else
Sleep(1);
GetSpace();
}
m_pBuffer->Stop();
}
void CWin32DirectSound::MapDataIntoBuffer(unsigned char* pData, unsigned int len, unsigned char* pOut)
{
// TODO: Add support for 8, 24, and 32-bit audio
if (m_pChannelMap && !m_Passthrough && m_uiBitsPerSample == 16)
{
short* pOutFrame = (short*)pOut;
for (short* pInFrame = (short*)pData;
pInFrame < (short*)pData + (len / sizeof(short));
pInFrame += m_uiChannels, pOutFrame += m_uiChannels)
{
// Remap a single frame
for (unsigned int chan = 0; chan < m_uiChannels; chan++)
pOutFrame[m_pChannelMap[chan]] = pInFrame[chan]; // Copy sample into correct position in the output buffer
}
}
else
{
memcpy(pOut, pData, len);
}
}
// Channel maps
// Our output order is FL, FR, C, LFE, SL, SR,
const unsigned char ac3_51_Map[] = {0,1,4,5,2,3}; // Sent as FL, FR, SL, SR, C, LFE
const unsigned char ac3_50_Map[] = {0,1,4,2,3}; // Sent as FL, FR, SL, SR, C
const unsigned char eac3_51_Map[] = {0,4,1,2,3,5}; // Sent as FL, C, FR, SL, SR, LFE
const unsigned char eac3_50_Map[] = {0,4,1,2,3}; // Sent as FL, C, FR, SL, SR, LFE
const unsigned char aac_51_Map[] = {4,0,1,2,3,5}; // Sent as C, FL, FR, SL, SR, LFE
const unsigned char aac_50_Map[] = {4,0,1,2,3}; // Sent as C, FL, FR, SL, SR
const unsigned char vorbis_51_Map[] = {0,4,1,2,3,5}; // Sent as FL, C, FR, SL, SR, LFE
const unsigned char vorbis_50_Map[] = {0,4,1,2,3}; // Sent as FL, C, FR, SL, SR
// TODO: This could be a lot more efficient
unsigned char* CWin32DirectSound::GetChannelMap(unsigned int channels, const char* strAudioCodec)
{
if (!strcmp(strAudioCodec, "AC3") || !strcmp(strAudioCodec, "DTS"))
{
if (channels == 6)
return (unsigned char*)ac3_51_Map;
else if (channels == 5)
return (unsigned char*)ac3_50_Map;
}
else if (!strcmp(strAudioCodec, "AAC"))
{
if (channels == 6)
return (unsigned char*)aac_51_Map;
else if (channels == 5)
return (unsigned char*)aac_50_Map;
}
else if (!strcmp(strAudioCodec, "Vorbis"))
{
if (channels == 6)
return (unsigned char*)vorbis_51_Map;
else if (channels == 5)
return (unsigned char*)vorbis_50_Map;
}
else if (!strcmp(strAudioCodec, "EAC3"))
{
if (channels == 6)
return (unsigned char*)eac3_51_Map;
else if (channels == 5)
return (unsigned char*)eac3_50_Map;
}
return NULL; // We don't know how to map this, so just leave it alone
}
//***********************************************************************************************
void CWin32DirectSound::SwitchChannels(int iAudioStream, bool bAudioOnAllSpeakers)
{
return;
}
//***********************************************************************************************
char * CWin32DirectSound::dserr2str(int err)
{
switch (err)
{
case DS_OK: return "DS_OK";
case DS_NO_VIRTUALIZATION: return "DS_NO_VIRTUALIZATION";
case DSERR_ALLOCATED: return "DS_NO_VIRTUALIZATION";
case DSERR_CONTROLUNAVAIL: return "DSERR_CONTROLUNAVAIL";
case DSERR_INVALIDPARAM: return "DSERR_INVALIDPARAM";
case DSERR_INVALIDCALL: return "DSERR_INVALIDCALL";
case DSERR_GENERIC: return "DSERR_GENERIC";
case DSERR_PRIOLEVELNEEDED: return "DSERR_PRIOLEVELNEEDED";
case DSERR_OUTOFMEMORY: return "DSERR_OUTOFMEMORY";
case DSERR_BADFORMAT: return "DSERR_BADFORMAT";
case DSERR_UNSUPPORTED: return "DSERR_UNSUPPORTED";
case DSERR_NODRIVER: return "DSERR_NODRIVER";
case DSERR_ALREADYINITIALIZED: return "DSERR_ALREADYINITIALIZED";
case DSERR_NOAGGREGATION: return "DSERR_NOAGGREGATION";
case DSERR_BUFFERLOST: return "DSERR_BUFFERLOST";
case DSERR_OTHERAPPHASPRIO: return "DSERR_OTHERAPPHASPRIO";
case DSERR_UNINITIALIZED: return "DSERR_UNINITIALIZED";
case DSERR_NOINTERFACE: return "DSERR_NOINTERFACE";
case DSERR_ACCESSDENIED: return "DSERR_ACCESSDENIED";
default: return "unknown";
}
}