pcsx2/plugins/spu2-x/src/SndOut_Portaudio.cpp

677 lines
19 KiB
C++

/* SPU2-X, A plugin for Emulating the Sound Processing Unit of the Playstation 2
* Developed and maintained by the Pcsx2 Development Team.
*
* Original portions from SPU2ghz are (c) 2008 by David Quintana [gigaherz]
*
* SPU2-X is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* SPU2-X 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with SPU2-X. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Global.h"
#define _WIN32_DCOM
#include "Dialogs.h"
#include "portaudio.h"
#include "wchar.h"
#include <vector>
#ifdef __WIN32__
#include "pa_win_wasapi.h"
#endif
int PaCallback( const void *inputBuffer, void *outputBuffer,
unsigned long framesPerBuffer,
const PaStreamCallbackTimeInfo* timeInfo,
PaStreamCallbackFlags statusFlags,
void *userData );
class Portaudio : public SndOutModule
{
private:
//////////////////////////////////////////////////////////////////////////////////////////
// Configuration Vars (unused still)
int m_ApiId;
wxString m_Device;
bool m_UseHardware;
bool m_WasapiExclusiveMode;
bool m_SuggestedLatencyMinimal;
int m_SuggestedLatencyMS;
//////////////////////////////////////////////////////////////////////////////////////////
// Instance vars
int writtenSoFar;
int writtenLastTime;
int availableLastTime;
int actualUsedChannels;
bool started;
PaStream* stream;
//////////////////////////////////////////////////////////////////////////////////////////
// Stuff necessary for speaker expansion
class SampleReader
{
public:
virtual int ReadSamples( const void *inputBuffer, void *outputBuffer,
unsigned long framesPerBuffer,
const PaStreamCallbackTimeInfo* timeInfo,
PaStreamCallbackFlags statusFlags,
void *userData ) = 0;
};
template<class T>
class ConvertedSampleReader : public SampleReader
{
int* written;
public:
ConvertedSampleReader(int* pWritten)
{
written = pWritten;
}
virtual int ReadSamples( const void *inputBuffer, void *outputBuffer,
unsigned long framesPerBuffer,
const PaStreamCallbackTimeInfo* timeInfo,
PaStreamCallbackFlags statusFlags,
void *userData )
{
T* p1 = (T*)outputBuffer;
int packets = framesPerBuffer / SndOutPacketSize;
for(int p=0; p<packets; p++, p1+=SndOutPacketSize )
SndBuffer::ReadSamples( p1 );
(*written) += packets * SndOutPacketSize;
return 0;
}
};
public:
SampleReader* ActualPaCallback;
Portaudio()
{
m_SuggestedLatencyMinimal = true;
m_UseHardware = false;
m_WasapiExclusiveMode = false;
started = false;
stream = NULL;
ActualPaCallback = NULL;
m_ApiId=-1;
m_SuggestedLatencyMS = 20;
actualUsedChannels = 0;
writtenSoFar = 0;
writtenLastTime = 0;
availableLastTime = 0;
}
s32 Init()
{
started=false;
stream=NULL;
ReadSettings();
PaError err = Pa_Initialize();
if( err != paNoError )
{
fprintf(stderr,"* SPU2-X: PortAudio error: %s\n", Pa_GetErrorText( err ) );
return -1;
}
started=true;
int deviceIndex = -1;
fprintf(stderr,"* SPU2-X: Enumerating PortAudio devices:\n");
for(int i=0, j=0;i<Pa_GetDeviceCount();i++)
{
const PaDeviceInfo * info = Pa_GetDeviceInfo(i);
if(info->maxOutputChannels > 0)
{
const PaHostApiInfo * apiinfo = Pa_GetHostApiInfo(info->hostApi);
fprintf(stderr," *** Device %d: '%s' (%s)", j, info->name, apiinfo->name);
if(apiinfo->type == m_ApiId)
{
if(m_Device == wxString::FromUTF8(info->name))
{
deviceIndex = i;
fprintf(stderr," (selected)");
}
}
fprintf(stderr,"\n");
j++;
}
}
fflush(stderr);
if(deviceIndex<0 && m_ApiId>=0)
{
for(int i=0;i<Pa_GetHostApiCount();i++)
{
const PaHostApiInfo * apiinfo = Pa_GetHostApiInfo(i);
if(apiinfo->type == m_ApiId)
{
deviceIndex = apiinfo->defaultOutputDevice;
}
}
}
if(deviceIndex>=0)
{
void* infoPtr = NULL;
const PaDeviceInfo * devinfo = Pa_GetDeviceInfo(deviceIndex);
int speakers;
switch(numSpeakers) // speakers = (numSpeakers + 1) *2; ?
{
case 0: speakers = 2; break; // Stereo
case 1: speakers = 4; break; // Quadrafonic
case 2: speakers = 6; break; // Surround 5.1
case 3: speakers = 8; break; // Surround 7.1
default: speakers = 2;
}
actualUsedChannels = std::min(speakers, devinfo->maxOutputChannels);
switch( actualUsedChannels )
{
case 2:
ConLog( "* SPU2 > Using normal 2 speaker stereo output.\n" );
ActualPaCallback = new ConvertedSampleReader<Stereo20Out32>(&writtenSoFar);
break;
case 3:
ConLog( "* SPU2 > 2.1 speaker expansion enabled.\n" );
ActualPaCallback = new ConvertedSampleReader<Stereo21Out32>(&writtenSoFar);
break;
case 4:
ConLog( "* SPU2 > 4 speaker expansion enabled [quadraphenia]\n" );
ActualPaCallback = new ConvertedSampleReader<Stereo40Out32>(&writtenSoFar);
break;
case 5:
ConLog( "* SPU2 > 4.1 speaker expansion enabled.\n" );
ActualPaCallback = new ConvertedSampleReader<Stereo41Out32>(&writtenSoFar);
break;
case 6:
case 7:
switch(dplLevel)
{
case 0:
ConLog( "* SPU2 > 5.1 speaker expansion enabled.\n" );
ActualPaCallback = new ConvertedSampleReader<Stereo51Out32>(&writtenSoFar); //"normal" stereo upmix
break;
case 1:
ConLog( "* SPU2 > 5.1 speaker expansion with basic ProLogic dematrixing enabled.\n" );
ActualPaCallback = new ConvertedSampleReader<Stereo51Out32Dpl>(&writtenSoFar); // basic Dpl decoder without rear stereo balancing
break;
case 2:
ConLog( "* SPU2 > 5.1 speaker expansion with experimental ProLogicII dematrixing enabled.\n" );
ActualPaCallback = new ConvertedSampleReader<Stereo51Out32DplII>(&writtenSoFar); //gigas PLII
break;
}
actualUsedChannels = 6; // we do not support 7.0 or 6.2 configurations, downgrade to 5.1
break;
default: // anything 8 or more gets the 7.1 treatment!
ConLog( "* SPU2 > 7.1 speaker expansion enabled.\n" );
ActualPaCallback = new ConvertedSampleReader<Stereo71Out32>(&writtenSoFar);
actualUsedChannels = 8; // we do not support 7.2 or more, downgrade to 7.1
break;
}
#ifdef __WIN32__
PaWasapiStreamInfo info = {
sizeof(PaWasapiStreamInfo),
paWASAPI,
1,
paWinWasapiExclusive
};
if((m_ApiId == paWASAPI) && m_WasapiExclusiveMode)
{
// Pass it the Exclusive mode enable flag
infoPtr = &info;
}
#endif
PaStreamParameters outParams = {
// PaDeviceIndex device;
// int channelCount;
// PaSampleFormat sampleFormat;
// PaTime suggestedLatency;
// void *hostApiSpecificStreamInfo;
deviceIndex,
actualUsedChannels,
paInt32,
m_SuggestedLatencyMinimal?(SndOutPacketSize/(float)SampleRate):(m_SuggestedLatencyMS/1000.0f),
infoPtr
};
err = Pa_OpenStream(&stream,
NULL, &outParams, SampleRate,
SndOutPacketSize,
paNoFlag,
PaCallback,
NULL);
}
else
{
err = Pa_OpenDefaultStream( &stream,
0, actualUsedChannels, paInt32, 48000,
SndOutPacketSize,
PaCallback,
NULL );
}
if( err != paNoError )
{
fprintf(stderr,"* SPU2-X: PortAudio error: %s\n", Pa_GetErrorText( err ) );
Pa_Terminate();
return -1;
}
err = Pa_StartStream( stream );
if( err != paNoError )
{
fprintf(stderr,"* SPU2-X: PortAudio error: %s\n", Pa_GetErrorText( err ) );
Pa_CloseStream(stream);
stream=NULL;
Pa_Terminate();
return -1;
}
return 0;
}
void Close()
{
PaError err;
if(started)
{
if(stream)
{
if(Pa_IsStreamActive(stream))
{
err = Pa_StopStream(stream);
if( err != paNoError )
fprintf(stderr,"* SPU2-X: PortAudio error: %s\n", Pa_GetErrorText( err ) );
}
err = Pa_CloseStream(stream);
if( err != paNoError )
fprintf(stderr,"* SPU2-X: PortAudio error: %s\n", Pa_GetErrorText( err ) );
stream=NULL;
}
// Seems to do more harm than good.
//PaError err = Pa_Terminate();
//if( err != paNoError )
// fprintf(stderr,"* SPU2-X: PortAudio error: %s\n", Pa_GetErrorText( err ) );
started=false;
}
}
////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////
#ifdef WIN32
private:
bool _ConfigProc(HWND hWnd,UINT uMsg,WPARAM wParam,LPARAM lParam)
{
int wmId,wmEvent;
int tSel = 0;
switch(uMsg)
{
case WM_INITDIALOG:
{
wchar_t temp[128];
SendMessage(GetDlgItem(hWnd,IDC_PA_DEVICE),CB_RESETCONTENT,0,0);
SendMessageA(GetDlgItem(hWnd,IDC_PA_DEVICE),CB_ADDSTRING,0,(LPARAM)"Default Device");
SendMessage(GetDlgItem(hWnd,IDC_PA_DEVICE),CB_SETCURSEL,0,0);
SendMessage(GetDlgItem(hWnd,IDC_PA_HOSTAPI),CB_RESETCONTENT,0,0);
SendMessageA(GetDlgItem(hWnd,IDC_PA_HOSTAPI),CB_ADDSTRING,0,(LPARAM)"Unspecified");
int idx = 0;
for(int i=0;i<Pa_GetHostApiCount();i++)
{
const PaHostApiInfo * apiinfo = Pa_GetHostApiInfo(i);
if(apiinfo->deviceCount > 0)
{
SendMessageA(GetDlgItem(hWnd,IDC_PA_HOSTAPI),CB_ADDSTRING,0,(LPARAM)apiinfo->name);
SendMessageA(GetDlgItem(hWnd,IDC_PA_HOSTAPI),CB_SETITEMDATA,i+1,apiinfo->type);
}
if(apiinfo->type == m_ApiId)
{
idx = i+1;
}
}
SendMessage(GetDlgItem(hWnd,IDC_PA_HOSTAPI),CB_SETCURSEL,idx,0);
if(idx > 0)
{
int api_idx = idx-1;
SendMessage(GetDlgItem(hWnd,IDC_PA_DEVICE),CB_RESETCONTENT,0,0);
SendMessageA(GetDlgItem(hWnd,IDC_PA_DEVICE),CB_ADDSTRING,0,(LPARAM)"Default Device");
SendMessage(GetDlgItem(hWnd,IDC_PA_DEVICE),CB_SETITEMDATA,0,0);
int _idx=0;
int i=1;
for(int j=0;j<Pa_GetDeviceCount();j++)
{
const PaDeviceInfo * info = Pa_GetDeviceInfo(j);
if(info->hostApi == api_idx && info->maxOutputChannels > 0)
{
SendMessage(GetDlgItem(hWnd,IDC_PA_DEVICE),CB_ADDSTRING,0,(LPARAM)wxString::FromUTF8(info->name).wc_str());
SendMessage(GetDlgItem(hWnd,IDC_PA_DEVICE),CB_SETITEMDATA,i,(LPARAM)info);
if(wxString::FromUTF8(info->name) == m_Device)
{
_idx = i;
}
i++;
}
}
SendMessage(GetDlgItem(hWnd,IDC_PA_DEVICE),CB_SETCURSEL,_idx,0);
}
INIT_SLIDER( IDC_LATENCY, 10, 200, 10, 1, 1 );
SendMessage(GetDlgItem(hWnd,IDC_LATENCY),TBM_SETPOS,TRUE,m_SuggestedLatencyMS);
swprintf_s(temp, L"%d ms",m_SuggestedLatencyMS);
SetWindowText(GetDlgItem(hWnd,IDC_LATENCY_LABEL),temp);
if(m_SuggestedLatencyMinimal)
SET_CHECK( IDC_MINIMIZE, true );
else
SET_CHECK( IDC_MANUAL, true );
SET_CHECK( IDC_EXCLUSIVE, m_WasapiExclusiveMode );
}
break;
case WM_COMMAND:
{
//wchar_t temp[128];
wmId = LOWORD(wParam);
wmEvent = HIWORD(wParam);
// Parse the menu selections:
switch (wmId)
{
case IDOK:
{
int idx = (int)SendMessage(GetDlgItem(hWnd,IDC_PA_HOSTAPI),CB_GETCURSEL,0,0);
m_ApiId = SendMessage(GetDlgItem(hWnd,IDC_PA_HOSTAPI),CB_GETITEMDATA,idx,0);
idx = (int)SendMessage(GetDlgItem(hWnd,IDC_PA_DEVICE),CB_GETCURSEL,0,0);
const PaDeviceInfo * info = (const PaDeviceInfo *)SendMessage(GetDlgItem(hWnd,IDC_PA_DEVICE),CB_GETITEMDATA,idx,0);
if(info)
m_Device = wxString::FromUTF8( info->name );
else
m_Device = L"default";
m_SuggestedLatencyMS = (int)SendMessage( GetDlgItem( hWnd, IDC_LATENCY ), TBM_GETPOS, 0, 0 );
if( m_SuggestedLatencyMS < 10 ) m_SuggestedLatencyMS = 10;
if( m_SuggestedLatencyMS > 200 ) m_SuggestedLatencyMS = 200;
m_SuggestedLatencyMinimal = SendMessage(GetDlgItem(hWnd,IDC_MINIMIZE),BM_GETCHECK,0,0)==BST_CHECKED;
m_WasapiExclusiveMode = SendMessage(GetDlgItem(hWnd,IDC_EXCLUSIVE),BM_GETCHECK,0,0)==BST_CHECKED;
EndDialog(hWnd,0);
}
break;
case IDCANCEL:
EndDialog(hWnd,0);
break;
case IDC_PA_HOSTAPI:
{
if(wmEvent == CBN_SELCHANGE)
{
int api_idx = (int)SendMessage(GetDlgItem(hWnd,IDC_PA_HOSTAPI),CB_GETCURSEL,0,0)-1;
int apiId = SendMessageA(GetDlgItem(hWnd,IDC_PA_HOSTAPI),CB_GETITEMDATA,api_idx,0);
SendMessage(GetDlgItem(hWnd,IDC_PA_DEVICE),CB_RESETCONTENT,0,0);
SendMessageA(GetDlgItem(hWnd,IDC_PA_DEVICE),CB_ADDSTRING,0,(LPARAM)"Default Device");
SendMessage(GetDlgItem(hWnd,IDC_PA_DEVICE),CB_SETITEMDATA,0,0);
int idx=0;
int i=1;
for(int j=0;j<Pa_GetDeviceCount();j++)
{
const PaDeviceInfo * info = Pa_GetDeviceInfo(j);
if(info->hostApi == api_idx && info->maxOutputChannels > 0)
{
SendMessage(GetDlgItem(hWnd,IDC_PA_DEVICE),CB_ADDSTRING,0,(LPARAM)wxString::FromUTF8(info->name).wc_str());
SendMessage(GetDlgItem(hWnd,IDC_PA_DEVICE),CB_SETITEMDATA,i,(LPARAM)info);
i++;
}
}
SendMessage(GetDlgItem(hWnd,IDC_PA_DEVICE),CB_SETCURSEL,idx,0);
}
}
break;
default:
return FALSE;
}
}
break;
case WM_HSCROLL:
{
wmId = LOWORD(wParam);
wmEvent = HIWORD(wParam);
switch(wmId)
{
//case TB_ENDTRACK:
//case TB_THUMBPOSITION:
case TB_LINEUP:
case TB_LINEDOWN:
case TB_PAGEUP:
case TB_PAGEDOWN:
wmEvent = (int)SendMessage((HWND)lParam,TBM_GETPOS,0,0);
case TB_THUMBTRACK:
{
wchar_t temp[128];
if( wmEvent < 10 ) wmEvent = 10;
if( wmEvent > 200 ) wmEvent = 200;
SendMessage((HWND)lParam,TBM_SETPOS,TRUE,wmEvent);
swprintf_s(temp, L"%d ms",wmEvent);
SetWindowText(GetDlgItem(hWnd,IDC_LATENCY_LABEL),temp);
break;
}
default:
return FALSE;
}
}
break;
default:
return FALSE;
}
return TRUE;
}
static BOOL CALLBACK ConfigProc(HWND hWnd,UINT uMsg,WPARAM wParam,LPARAM lParam);
static BOOL CALLBACK DSEnumCallback( LPGUID lpGuid, LPCTSTR lpcstrDescription, LPCTSTR lpcstrModule, LPVOID lpContext );
public:
virtual void Configure(uptr parent)
{
PaError err = Pa_Initialize(); // Initialization can be done multiple times, PA keeps a counter
if( err != paNoError )
{
fprintf(stderr,"* SPU2-X: PortAudio error: %s\n", Pa_GetErrorText( err ) );
return;
}
// keep portaudio initialized until the dialog closes
INT_PTR ret;
ret=DialogBoxParam(hInstance,MAKEINTRESOURCE(IDD_PORTAUDIO),(HWND)parent,(DLGPROC)ConfigProc,1);
if(ret==-1)
{
MessageBox((HWND)parent,L"Error Opening the config dialog.",L"OMG ERROR!",MB_OK | MB_SETFOREGROUND);
return;
}
Pa_Terminate();
}
#else
virtual void Configure(uptr parent)
{
}
#endif
s32 Test() const
{
return 0;
}
int GetEmptySampleCount()
{
long availableNow = Pa_GetStreamWriteAvailable(stream);
int playedSinceLastTime = (writtenSoFar - writtenLastTime) + (availableNow - availableLastTime);
writtenLastTime = writtenSoFar;
availableLastTime = availableNow;
// Lowest resolution here is the SndOutPacketSize we use.
return playedSinceLastTime;
}
const wchar_t* GetIdent() const
{
return L"portaudio";
}
const wchar_t* GetLongName() const
{
return L"Portaudio (crossplatform)";
}
void ReadSettings()
{
wxString api( L"EMPTYEMPTYEMPTY" );
m_Device = L"EMPTYEMPTYEMPTY";
#ifdef __linux__
// By default on linux use the ALSA API (+99% users) -- Gregory
CfgReadStr( L"PORTAUDIO", L"HostApi", api, L"ALSA" );
#elif defined(__APPLE__)
// Suppose OSX only has CoreAudio...
CfgReadStr( L"PORTAUDIO", L"HostApi", api, L"CoreAudio" );
#else
CfgReadStr( L"PORTAUDIO", L"HostApi", api, L"WASAPI" );
#endif
CfgReadStr( L"PORTAUDIO", L"Device", m_Device, L"default" );
SetApiSettings(api);
m_WasapiExclusiveMode = CfgReadBool( L"PORTAUDIO", L"Wasapi_Exclusive_Mode", false);
m_SuggestedLatencyMinimal = CfgReadBool( L"PORTAUDIO", L"Minimal_Suggested_Latency", true);
m_SuggestedLatencyMS = CfgReadInt( L"PORTAUDIO", L"Manual_Suggested_Latency_MS", 20);
if( m_SuggestedLatencyMS < 10 ) m_SuggestedLatencyMS = 10;
if( m_SuggestedLatencyMS > 200 ) m_SuggestedLatencyMS = 200;
}
void SetApiSettings(wxString api)
{
m_ApiId = -1;
if(api == L"InDevelopment") m_ApiId = paInDevelopment; /* use while developing support for a new host API */
if(api == L"DirectSound") m_ApiId = paDirectSound;
if(api == L"MME") m_ApiId = paMME;
if(api == L"ASIO") m_ApiId = paASIO;
if(api == L"SoundManager") m_ApiId = paSoundManager;
if(api == L"CoreAudio") m_ApiId = paCoreAudio;
if(api == L"OSS") m_ApiId = paOSS;
if(api == L"ALSA") m_ApiId = paALSA;
if(api == L"AL") m_ApiId = paAL;
if(api == L"BeOS") m_ApiId = paBeOS;
if(api == L"WDMKS") m_ApiId = paWDMKS;
if(api == L"JACK") m_ApiId = paJACK;
if(api == L"WASAPI") m_ApiId = paWASAPI;
if(api == L"AudioScienceHPI") m_ApiId = paAudioScienceHPI;
}
void WriteSettings() const
{
wxString api;
switch(m_ApiId)
{
case paInDevelopment: api = L"InDevelopment"; break; /* use while developing support for a new host API */
case paDirectSound: api = L"DirectSound"; break;
case paMME: api = L"MME"; break;
case paASIO: api = L"ASIO"; break;
case paSoundManager: api = L"SoundManager"; break;
case paCoreAudio: api = L"CoreAudio"; break;
case paOSS: api = L"OSS"; break;
case paALSA: api = L"ALSA"; break;
case paAL: api = L"AL"; break;
case paBeOS: api = L"BeOS"; break;
case paWDMKS: api = L"WDMKS"; break;
case paJACK: api = L"JACK"; break;
case paWASAPI: api = L"WASAPI"; break;
case paAudioScienceHPI: api = L"AudioScienceHPI"; break;
default: api = L"Unknown";
}
CfgWriteStr( L"PORTAUDIO", L"HostApi", api);
CfgWriteStr( L"PORTAUDIO", L"Device", m_Device);
CfgWriteBool( L"PORTAUDIO", L"Wasapi_Exclusive_Mode", m_WasapiExclusiveMode);
CfgWriteBool( L"PORTAUDIO", L"Minimal_Suggested_Latency", m_SuggestedLatencyMinimal);
CfgWriteInt( L"PORTAUDIO", L"Manual_Suggested_Latency_MS", m_SuggestedLatencyMS);
}
} static PA;
int PaCallback( const void *inputBuffer, void *outputBuffer,
unsigned long framesPerBuffer,
const PaStreamCallbackTimeInfo* timeInfo,
PaStreamCallbackFlags statusFlags,
void *userData )
{
return PA.ActualPaCallback->ReadSamples(inputBuffer,outputBuffer,framesPerBuffer,timeInfo,statusFlags,userData);
}
#ifdef WIN32
BOOL CALLBACK Portaudio::ConfigProc(HWND hWnd,UINT uMsg,WPARAM wParam,LPARAM lParam)
{
return PA._ConfigProc( hWnd, uMsg, wParam, lParam );
}
#endif
SndOutModule *PortaudioOut = &PA;