Files
winamp/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDevicePortAudio.cpp
2024-09-24 14:54:57 +02:00

1082 lines
36 KiB
C++

/* SPDX-License-Identifier: BSD-3-Clause */
/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
#include "openmpt/all/BuildSettings.hpp"
#include "SoundDevicePortAudio.hpp"
#include "SoundDevice.hpp"
#include "mpt/base/detect.hpp"
#include "mpt/base/macros.hpp"
#include "mpt/base/saturate_cast.hpp"
#include "mpt/base/saturate_round.hpp"
#include "mpt/format/message_macros.hpp"
#include "mpt/format/simple.hpp"
#include "mpt/parse/parse.hpp"
#include "mpt/string/buffer.hpp"
#include "mpt/string/types.hpp"
#include "mpt/string_transcode/transcode.hpp"
#include "openmpt/base/Types.hpp"
#include "openmpt/logging/Logger.hpp"
#include "openmpt/soundbase/SampleFormat.hpp"
#include <array>
#include <vector>
#include <cstddef>
#ifdef MPT_WITH_PORTAUDIO
#if defined(MODPLUG_TRACKER) && MPT_COMPILER_MSVC
#include "../include/portaudio/src/common/pa_debugprint.h"
#endif
#if MPT_OS_WINDOWS
#include <shellapi.h>
#include <windows.h>
#endif
#endif
OPENMPT_NAMESPACE_BEGIN
namespace SoundDevice
{
#ifdef MPT_WITH_PORTAUDIO
#ifdef MPT_ALL_LOGGING
#define PALOG(x) MPT_LOG(GetLogger(), LogDebug, "PortAudio", x)
#define PA_LOG_ENABLED 1
#else
#define PALOG(x) \
do \
{ \
} while(0)
#define PA_LOG_ENABLED 0
#endif
CPortaudioDevice::CPortaudioDevice(ILogger &logger, SoundDevice::Info info, SoundDevice::SysInfo sysInfo)
: SoundDevice::Base(logger, info, sysInfo)
, m_StatisticPeriodFrames(0)
{
mpt::ustring internalID = GetDeviceInternalID();
if(internalID == MPT_USTRING("WASAPI-Default"))
{
m_DeviceIsDefault = true;
m_DeviceIndex = Pa_GetHostApiInfo(Pa_HostApiTypeIdToHostApiIndex(paWASAPI))->defaultOutputDevice;
} else
{
m_DeviceIsDefault = false;
m_DeviceIndex = mpt::ConvertStringTo<PaDeviceIndex>(internalID);
}
m_HostApiType = Pa_GetHostApiInfo(Pa_GetDeviceInfo(m_DeviceIndex)->hostApi)->type;
m_StreamParameters = {};
m_InputStreamParameters = {};
#if MPT_OS_WINDOWS
m_WasapiStreamInfo = {};
#endif // MPT_OS_WINDOWS
m_Stream = 0;
m_StreamInfo = 0;
m_CurrentFrameBuffer = nullptr;
m_CurrentFrameBufferInput = nullptr;
m_CurrentFrameCount = 0;
m_CurrentRealLatency = 0.0;
}
CPortaudioDevice::~CPortaudioDevice()
{
Close();
}
bool CPortaudioDevice::InternalOpen()
{
m_StreamParameters = {};
m_InputStreamParameters = {};
m_Stream = 0;
m_StreamInfo = 0;
m_CurrentFrameBuffer = 0;
m_CurrentFrameBufferInput = 0;
m_CurrentFrameCount = 0;
m_StreamParameters.device = m_DeviceIndex;
if(m_StreamParameters.device == -1)
{
return false;
}
m_StreamParameters.channelCount = m_Settings.Channels;
if(m_Settings.sampleFormat.IsFloat())
{
if(m_Settings.sampleFormat.GetBitsPerSample() != 32)
{
return false;
}
m_StreamParameters.sampleFormat = paFloat32;
} else
{
switch(m_Settings.sampleFormat.GetBitsPerSample())
{
case 8: m_StreamParameters.sampleFormat = paInt8; break;
case 16: m_StreamParameters.sampleFormat = paInt16; break;
case 24: m_StreamParameters.sampleFormat = paInt24; break;
case 32: m_StreamParameters.sampleFormat = paInt32; break;
default: return false; break;
}
}
m_StreamParameters.suggestedLatency = m_Settings.Latency;
m_StreamParameters.hostApiSpecificStreamInfo = NULL;
unsigned long framesPerBuffer = static_cast<long>(m_Settings.UpdateInterval * m_Settings.Samplerate);
if(m_HostApiType == paWASAPI)
{
#if MPT_OS_WINDOWS
m_WasapiStreamInfo = {};
m_WasapiStreamInfo.size = sizeof(PaWasapiStreamInfo);
m_WasapiStreamInfo.hostApiType = paWASAPI;
m_WasapiStreamInfo.version = 1;
if(m_Settings.BoostThreadPriority)
{
m_WasapiStreamInfo.flags |= paWinWasapiThreadPriority;
if(GetAppInfo().BoostedThreadMMCSSClassVista == MPT_USTRING(""))
{
m_WasapiStreamInfo.threadPriority = eThreadPriorityNone;
} else if(GetAppInfo().BoostedThreadMMCSSClassVista == MPT_USTRING("Audio"))
{
m_WasapiStreamInfo.threadPriority = eThreadPriorityAudio;
} else if(GetAppInfo().BoostedThreadMMCSSClassVista == MPT_USTRING("Capture"))
{
m_WasapiStreamInfo.threadPriority = eThreadPriorityCapture;
} else if(GetAppInfo().BoostedThreadMMCSSClassVista == MPT_USTRING("Distribution"))
{
m_WasapiStreamInfo.threadPriority = eThreadPriorityDistribution;
} else if(GetAppInfo().BoostedThreadMMCSSClassVista == MPT_USTRING("Games"))
{
m_WasapiStreamInfo.threadPriority = eThreadPriorityGames;
} else if(GetAppInfo().BoostedThreadMMCSSClassVista == MPT_USTRING("Playback"))
{
m_WasapiStreamInfo.threadPriority = eThreadPriorityPlayback;
} else if(GetAppInfo().BoostedThreadMMCSSClassVista == MPT_USTRING("Pro Audio"))
{
m_WasapiStreamInfo.threadPriority = eThreadPriorityProAudio;
} else if(GetAppInfo().BoostedThreadMMCSSClassVista == MPT_USTRING("Window Manager"))
{
m_WasapiStreamInfo.threadPriority = eThreadPriorityWindowManager;
} else
{
m_WasapiStreamInfo.threadPriority = eThreadPriorityNone;
}
m_StreamParameters.hostApiSpecificStreamInfo = &m_WasapiStreamInfo;
}
#endif // MPT_OS_WINDOWS
if(m_Settings.ExclusiveMode)
{
m_Flags.WantsClippedOutput = false;
#if MPT_OS_WINDOWS
m_WasapiStreamInfo.flags |= paWinWasapiExclusive | paWinWasapiExplicitSampleFormat;
m_StreamParameters.hostApiSpecificStreamInfo = &m_WasapiStreamInfo;
#endif // MPT_OS_WINDOWS
} else
{
m_Flags.WantsClippedOutput = GetSysInfo().IsOriginal();
}
} else if(m_HostApiType == paWDMKS)
{
m_Flags.WantsClippedOutput = false;
framesPerBuffer = paFramesPerBufferUnspecified; // let portaudio choose
} else if(m_HostApiType == paMME)
{
m_Flags.WantsClippedOutput = (GetSysInfo().IsOriginal() && GetSysInfo().WindowsVersion.IsAtLeast(mpt::osinfo::windows::Version::WinVista));
} else if(m_HostApiType == paDirectSound)
{
m_Flags.WantsClippedOutput = (GetSysInfo().IsOriginal() && GetSysInfo().WindowsVersion.IsAtLeast(mpt::osinfo::windows::Version::WinVista));
} else
{
m_Flags.WantsClippedOutput = false;
}
m_InputStreamParameters = m_StreamParameters;
if(!HasInputChannelsOnSameDevice())
{
m_InputStreamParameters.device = static_cast<PaDeviceIndex>(m_Settings.InputSourceID);
}
m_InputStreamParameters.channelCount = m_Settings.InputChannels;
if(Pa_IsFormatSupported((m_Settings.InputChannels > 0) ? &m_InputStreamParameters : NULL, &m_StreamParameters, m_Settings.Samplerate) != paFormatIsSupported)
{
if(m_HostApiType == paWASAPI)
{
if(m_Settings.ExclusiveMode)
{
#if MPT_OS_WINDOWS
m_WasapiStreamInfo.flags &= ~paWinWasapiExplicitSampleFormat;
m_StreamParameters.hostApiSpecificStreamInfo = &m_WasapiStreamInfo;
#endif // MPT_OS_WINDOWS
if(Pa_IsFormatSupported((m_Settings.InputChannels > 0) ? &m_InputStreamParameters : NULL, &m_StreamParameters, m_Settings.Samplerate) != paFormatIsSupported)
{
return false;
}
} else
{
if(!GetSysInfo().IsWine && GetSysInfo().WindowsVersion.IsAtLeast(mpt::osinfo::windows::Version::Win7))
{ // retry with automatic stream format conversion (i.e. resampling)
#if MPT_OS_WINDOWS
m_WasapiStreamInfo.flags |= paWinWasapiAutoConvert;
m_StreamParameters.hostApiSpecificStreamInfo = &m_WasapiStreamInfo;
#endif // MPT_OS_WINDOWS
if(Pa_IsFormatSupported((m_Settings.InputChannels > 0) ? &m_InputStreamParameters : NULL, &m_StreamParameters, m_Settings.Samplerate) != paFormatIsSupported)
{
return false;
}
} else
{
return false;
}
}
} else
{
return false;
}
}
PaStreamFlags flags = paNoFlag;
if(m_Settings.DitherType == 0)
{
flags |= paDitherOff;
}
if(Pa_OpenStream(&m_Stream, (m_Settings.InputChannels > 0) ? &m_InputStreamParameters : NULL, &m_StreamParameters, m_Settings.Samplerate, framesPerBuffer, flags, StreamCallbackWrapper, reinterpret_cast<void *>(this)) != paNoError)
{
return false;
}
m_StreamInfo = Pa_GetStreamInfo(m_Stream);
if(!m_StreamInfo)
{
Pa_CloseStream(m_Stream);
m_Stream = 0;
return false;
}
return true;
}
bool CPortaudioDevice::InternalClose()
{
if(m_Stream)
{
const SoundDevice::BufferAttributes bufferAttributes = GetEffectiveBufferAttributes();
Pa_AbortStream(m_Stream);
Pa_CloseStream(m_Stream);
if(Pa_GetDeviceInfo(m_StreamParameters.device)->hostApi == Pa_HostApiTypeIdToHostApiIndex(paWDMKS))
{
Pa_Sleep(mpt::saturate_round<long>(bufferAttributes.Latency * 2.0 * 1000.0 + 0.5)); // wait for broken wdm drivers not closing the stream immediatly
}
m_StreamParameters = {};
m_InputStreamParameters = {};
m_StreamInfo = 0;
m_Stream = 0;
m_CurrentFrameCount = 0;
m_CurrentFrameBuffer = 0;
m_CurrentFrameBufferInput = 0;
}
return true;
}
bool CPortaudioDevice::InternalStart()
{
return Pa_StartStream(m_Stream) == paNoError;
}
void CPortaudioDevice::InternalStop()
{
Pa_StopStream(m_Stream);
}
void CPortaudioDevice::InternalFillAudioBuffer()
{
if(m_CurrentFrameCount == 0)
{
return;
}
CallbackLockedAudioReadPrepare(m_CurrentFrameCount, mpt::saturate_cast<std::size_t>(mpt::saturate_round<int64>(m_CurrentRealLatency * m_StreamInfo->sampleRate)));
CallbackLockedAudioProcessVoid(m_CurrentFrameBuffer, m_CurrentFrameBufferInput, m_CurrentFrameCount);
m_StatisticPeriodFrames.store(m_CurrentFrameCount);
CallbackLockedAudioProcessDone();
}
int64 CPortaudioDevice::InternalGetStreamPositionFrames() const
{
if(Pa_IsStreamActive(m_Stream) != 1)
{
return 0;
}
return static_cast<int64>(Pa_GetStreamTime(m_Stream) * m_StreamInfo->sampleRate);
}
SoundDevice::BufferAttributes CPortaudioDevice::InternalGetEffectiveBufferAttributes() const
{
SoundDevice::BufferAttributes bufferAttributes;
bufferAttributes.Latency = m_StreamInfo->outputLatency;
bufferAttributes.UpdateInterval = m_Settings.UpdateInterval;
bufferAttributes.NumBuffers = 1;
if(m_HostApiType == paWASAPI && m_Settings.ExclusiveMode)
{
// WASAPI exclusive mode streams only account for a single period of latency in PortAudio
// (i.e. the same way as Steinerg ASIO defines latency).
// That does not match our definition of latency, repair it.
bufferAttributes.Latency *= 2.0;
}
return bufferAttributes;
}
bool CPortaudioDevice::OnIdle()
{
if(!IsPlaying())
{
return false;
}
if(m_Stream)
{
if(m_HostApiType == paWDMKS)
{
// Catch timeouts in PortAudio threading code that cause the thread to exit.
// Restore our desired playback state by resetting the whole sound device.
if(Pa_IsStreamActive(m_Stream) <= 0)
{
// Hung state tends to be caused by an overloaded system.
// Sleeping too long would freeze the UI,
// but at least sleep a tiny bit of time to let things settle down.
const SoundDevice::BufferAttributes bufferAttributes = GetEffectiveBufferAttributes();
Pa_Sleep(mpt::saturate_round<long>(bufferAttributes.Latency * 2.0 * 1000.0 + 0.5));
RequestReset();
return true;
}
}
}
return false;
}
SoundDevice::Statistics CPortaudioDevice::GetStatistics() const
{
MPT_SOUNDDEV_TRACE_SCOPE();
SoundDevice::Statistics result;
result.InstantaneousLatency = m_CurrentRealLatency;
result.LastUpdateInterval = 1.0 * m_StatisticPeriodFrames / m_Settings.Samplerate;
result.text = mpt::ustring();
#if MPT_OS_WINDOWS
if(m_HostApiType == paWASAPI)
{
if(m_Settings.ExclusiveMode)
{
if(m_StreamParameters.hostApiSpecificStreamInfo && (m_WasapiStreamInfo.flags & paWinWasapiExplicitSampleFormat))
{
result.text += MPT_USTRING("Exclusive stream.");
} else
{
result.text += MPT_USTRING("Exclusive stream with sample format conversion.");
}
} else
{
if(m_StreamParameters.hostApiSpecificStreamInfo && (m_WasapiStreamInfo.flags & paWinWasapiAutoConvert))
{
result.text += MPT_USTRING("WASAPI stream resampling.");
} else
{
result.text += MPT_USTRING("No resampling.");
}
}
}
#endif // MPT_OS_WINDOWS
return result;
}
SoundDevice::Caps CPortaudioDevice::InternalGetDeviceCaps()
{
SoundDevice::Caps caps;
caps.Available = true;
caps.CanUpdateInterval = true;
caps.CanSampleFormat = true;
caps.CanExclusiveMode = false;
caps.CanBoostThreadPriority = false;
caps.CanUseHardwareTiming = false;
caps.CanChannelMapping = false;
caps.CanInput = false;
caps.HasNamedInputSources = false;
caps.CanDriverPanel = false;
caps.HasInternalDither = true;
caps.DefaultSettings.sampleFormat = SampleFormat::Float32;
const PaDeviceInfo *deviceInfo = Pa_GetDeviceInfo(m_DeviceIndex);
if(deviceInfo)
{
caps.DefaultSettings.Latency = deviceInfo->defaultLowOutputLatency;
}
if(HasInputChannelsOnSameDevice())
{
caps.CanInput = true;
caps.HasNamedInputSources = false;
} else
{
caps.CanInput = (EnumerateInputOnlyDevices(m_HostApiType).size() > 0);
caps.HasNamedInputSources = caps.CanInput;
}
if(m_HostApiType == paWASAPI)
{
caps.CanBoostThreadPriority = true;
caps.CanDriverPanel = true;
caps.DefaultSettings.sampleFormat = SampleFormat::Float32;
if(m_DeviceIsDefault)
{
caps.CanExclusiveMode = false;
caps.DefaultSettings.Latency = 0.030;
caps.DefaultSettings.UpdateInterval = 0.010;
} else
{
caps.CanExclusiveMode = true;
if(deviceInfo)
{
// PortAudio WASAPI returns the device period as latency
caps.DefaultSettings.Latency = deviceInfo->defaultHighOutputLatency * 2.0;
caps.DefaultSettings.UpdateInterval = deviceInfo->defaultHighOutputLatency;
}
}
} else if(m_HostApiType == paWDMKS)
{
caps.CanUpdateInterval = false;
caps.DefaultSettings.sampleFormat = SampleFormat::Int32;
} else if(m_HostApiType == paDirectSound)
{
if(GetSysInfo().IsOriginal() && GetSysInfo().WindowsVersion.IsAtLeast(mpt::osinfo::windows::Version::WinVista))
{
caps.DefaultSettings.sampleFormat = SampleFormat::Float32;
} else
{
caps.DefaultSettings.sampleFormat = SampleFormat::Int16;
}
} else if(m_HostApiType == paMME)
{
if(GetSysInfo().IsWine)
{
caps.DefaultSettings.sampleFormat = SampleFormat::Int16;
} else if(GetSysInfo().WindowsVersion.IsAtLeast(mpt::osinfo::windows::Version::WinVista))
{
caps.DefaultSettings.sampleFormat = SampleFormat::Float32;
} else
{
caps.DefaultSettings.sampleFormat = SampleFormat::Int16;
}
} else if(m_HostApiType == paASIO)
{
caps.DefaultSettings.sampleFormat = SampleFormat::Int32;
}
if(m_HostApiType == paDirectSound)
{
if(GetSysInfo().IsOriginal() && GetSysInfo().WindowsVersion.IsAtLeast(mpt::osinfo::windows::Version::WinVista))
{
caps.HasInternalDither = false;
}
} else if(m_HostApiType == paMME)
{
if(GetSysInfo().IsOriginal() && GetSysInfo().WindowsVersion.IsAtLeast(mpt::osinfo::windows::Version::WinVista))
{
caps.HasInternalDither = false;
}
} else if(m_HostApiType == paJACK)
{
caps.HasInternalDither = false;
} else if(m_HostApiType == paWASAPI)
{
caps.HasInternalDither = false;
}
return caps;
}
SoundDevice::DynamicCaps CPortaudioDevice::GetDeviceDynamicCaps(const std::vector<uint32> &baseSampleRates)
{
SoundDevice::DynamicCaps caps;
PaDeviceIndex device = m_DeviceIndex;
if(device == paNoDevice)
{
return caps;
}
for(std::size_t n = 0; n < baseSampleRates.size(); n++)
{
PaStreamParameters StreamParameters = {};
StreamParameters.device = device;
StreamParameters.channelCount = 2;
StreamParameters.sampleFormat = paInt16;
if(m_HostApiType == paWASAPI)
{
StreamParameters.sampleFormat = paFloat32;
}
StreamParameters.suggestedLatency = 0.0;
StreamParameters.hostApiSpecificStreamInfo = NULL;
if(Pa_IsFormatSupported(NULL, &StreamParameters, baseSampleRates[n]) == paFormatIsSupported)
{
caps.supportedSampleRates.push_back(baseSampleRates[n]);
if(!((m_HostApiType == paWASAPI) && m_DeviceIsDefault))
{
caps.supportedExclusiveSampleRates.push_back(baseSampleRates[n]);
}
}
}
if(m_HostApiType == paDirectSound)
{
if(GetSysInfo().IsOriginal() && GetSysInfo().WindowsVersion.IsAtLeast(mpt::osinfo::windows::Version::WinVista))
{
caps.supportedSampleFormats = {SampleFormat::Float32};
}
} else if(m_HostApiType == paMME)
{
if(GetSysInfo().IsOriginal() && GetSysInfo().WindowsVersion.IsAtLeast(mpt::osinfo::windows::Version::WinVista))
{
caps.supportedSampleFormats = {SampleFormat::Float32};
}
} else if(m_HostApiType == paJACK)
{
caps.supportedSampleFormats = {SampleFormat::Float32};
} else if(m_HostApiType == paWASAPI)
{
caps.supportedSampleFormats = {SampleFormat::Float32};
}
#if MPT_OS_WINDOWS
if(m_HostApiType == paWASAPI && !m_DeviceIsDefault)
{
caps.supportedExclusiveModeSampleFormats.clear();
const std::array<SampleFormat, 5> sampleFormats{SampleFormat::Int8, SampleFormat::Int16, SampleFormat::Int24, SampleFormat::Int32, SampleFormat::Float32};
for(const SampleFormat sampleFormat : sampleFormats)
{
for(const auto sampleRate : caps.supportedExclusiveSampleRates)
{
PaStreamParameters StreamParameters = {};
StreamParameters.device = device;
StreamParameters.channelCount = 2;
if(sampleFormat.IsFloat())
{
StreamParameters.sampleFormat = paFloat32;
} else
{
switch(sampleFormat.GetBitsPerSample())
{
case 8: StreamParameters.sampleFormat = paInt8; break;
case 16: StreamParameters.sampleFormat = paInt16; break;
case 24: StreamParameters.sampleFormat = paInt24; break;
case 32: StreamParameters.sampleFormat = paInt32; break;
}
}
StreamParameters.suggestedLatency = 0.0;
StreamParameters.hostApiSpecificStreamInfo = NULL;
PaWasapiStreamInfo wasapiStreamInfo = {};
wasapiStreamInfo.size = sizeof(PaWasapiStreamInfo);
wasapiStreamInfo.hostApiType = paWASAPI;
wasapiStreamInfo.version = 1;
wasapiStreamInfo.flags = paWinWasapiExclusive | paWinWasapiExplicitSampleFormat;
StreamParameters.hostApiSpecificStreamInfo = &wasapiStreamInfo;
if(Pa_IsFormatSupported(NULL, &StreamParameters, sampleRate) == paFormatIsSupported)
{
caps.supportedExclusiveModeSampleFormats.push_back(sampleFormat);
break;
}
}
}
}
if(m_HostApiType == paWDMKS)
{
caps.supportedSampleFormats.clear();
caps.supportedExclusiveModeSampleFormats.clear();
const std::array<SampleFormat, 5> sampleFormats{SampleFormat::Int8, SampleFormat::Int16, SampleFormat::Int24, SampleFormat::Int32, SampleFormat::Float32};
for(const SampleFormat sampleFormat : sampleFormats)
{
for(const auto sampleRate : caps.supportedSampleRates)
{
PaStreamParameters StreamParameters = {};
StreamParameters.device = device;
StreamParameters.channelCount = 2;
if(sampleFormat.IsFloat())
{
StreamParameters.sampleFormat = paFloat32;
} else
{
switch(sampleFormat.GetBitsPerSample())
{
case 8: StreamParameters.sampleFormat = paInt8; break;
case 16: StreamParameters.sampleFormat = paInt16; break;
case 24: StreamParameters.sampleFormat = paInt24; break;
case 32: StreamParameters.sampleFormat = paInt32; break;
}
}
StreamParameters.suggestedLatency = 0.0;
StreamParameters.hostApiSpecificStreamInfo = NULL;
if(Pa_IsFormatSupported(NULL, &StreamParameters, sampleRate) == paFormatIsSupported)
{
caps.supportedSampleFormats.push_back(sampleFormat);
caps.supportedExclusiveModeSampleFormats.push_back(sampleFormat);
break;
}
}
}
if(caps.supportedSampleFormats.empty())
{
caps.supportedSampleFormats = DefaultSampleFormats<std::vector<SampleFormat>>();
}
if(caps.supportedExclusiveModeSampleFormats.empty())
{
caps.supportedExclusiveModeSampleFormats = DefaultSampleFormats<std::vector<SampleFormat>>();
}
}
#endif // MPT_OS_WINDOWS
#if MPT_OS_WINDOWS
if((m_HostApiType == paWASAPI) && GetSysInfo().WindowsVersion.IsAtLeast(mpt::osinfo::windows::Version::Win7))
{
caps.supportedSampleRates = baseSampleRates;
}
#endif // MPT_OS_WINDOWS
if(!HasInputChannelsOnSameDevice())
{
caps.inputSourceNames.clear();
auto inputDevices = EnumerateInputOnlyDevices(m_HostApiType);
for(const auto &dev : inputDevices)
{
caps.inputSourceNames.push_back(std::make_pair(static_cast<uint32>(dev.first), dev.second));
}
}
return caps;
}
bool CPortaudioDevice::OpenDriverSettings()
{
#if MPT_OS_WINDOWS
if(m_HostApiType != paWASAPI)
{
return false;
}
bool hasVista = GetSysInfo().WindowsVersion.IsAtLeast(mpt::osinfo::windows::Version::WinVista);
mpt::winstring controlEXE;
TCHAR systemDir[MAX_PATH] = {};
if(GetSystemDirectory(systemDir, mpt::saturate_cast<UINT>(std::size(systemDir))) > 0)
{
controlEXE += mpt::ReadWinBuf(systemDir);
controlEXE += TEXT("\\");
}
controlEXE += TEXT("control.exe");
return (reinterpret_cast<INT_PTR>(ShellExecute(NULL, TEXT("open"), controlEXE.c_str(), (hasVista ? TEXT("/name Microsoft.Sound") : TEXT("mmsys.cpl")), NULL, SW_SHOW)) >= 32);
#else // !MPT_OS_WINDOWS
return false;
#endif // MPT_OS_WINDOWS
}
int CPortaudioDevice::StreamCallback(
const void *input, void *output, unsigned long frameCount, const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags)
{
if(!input && !output)
{
return paAbort;
}
if(m_HostApiType == paWDMKS)
{
// For WDM-KS, timeInfo->outputBufferDacTime seems to contain bogus values.
// Work-around it by using the slightly less accurate per-stream latency estimation.
m_CurrentRealLatency = m_StreamInfo->outputLatency;
} else if(m_HostApiType == paWASAPI)
{
// PortAudio latency calculation appears to miss the current period or chunk for WASAPI. Compensate it.
m_CurrentRealLatency = timeInfo->outputBufferDacTime - timeInfo->currentTime + (static_cast<double>(frameCount) / static_cast<double>(m_Settings.Samplerate));
} else if(m_HostApiType == paDirectSound)
{
// PortAudio latency calculation appears to miss the buffering latency.
// The current chunk, however, appears to be compensated for.
// Repair the confusion.
m_CurrentRealLatency = timeInfo->outputBufferDacTime - timeInfo->currentTime + m_StreamInfo->outputLatency - (static_cast<double>(frameCount) / static_cast<double>(m_Settings.Samplerate));
} else if(m_HostApiType == paALSA)
{
// PortAudio latency calculation appears to miss the buffering latency.
// The current chunk, however, appears to be compensated for.
// Repair the confusion.
m_CurrentRealLatency = timeInfo->outputBufferDacTime - timeInfo->currentTime + m_StreamInfo->outputLatency - (static_cast<double>(frameCount) / static_cast<double>(m_Settings.Samplerate));
} else
{
m_CurrentRealLatency = timeInfo->outputBufferDacTime - timeInfo->currentTime;
}
m_CurrentFrameBuffer = output;
m_CurrentFrameBufferInput = input;
m_CurrentFrameCount = frameCount;
CallbackFillAudioBufferLocked();
m_CurrentFrameCount = 0;
m_CurrentFrameBuffer = 0;
m_CurrentFrameBufferInput = 0;
if((m_HostApiType == paALSA) && (statusFlags & paOutputUnderflow))
{
// PortAudio ALSA does not recover well from buffer underruns
RequestRestart();
}
return paContinue;
}
int CPortaudioDevice::StreamCallbackWrapper(
const void *input, void *output, unsigned long frameCount, const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags, void *userData)
{
return reinterpret_cast<CPortaudioDevice *>(userData)->StreamCallback(input, output, frameCount, timeInfo, statusFlags);
}
std::vector<SoundDevice::Info> CPortaudioDevice::EnumerateDevices(ILogger &logger, SoundDevice::SysInfo sysInfo)
{
#if PA_LOG_ENABLED
auto GetLogger = [&]() -> ILogger &
{
return logger;
};
#else
MPT_UNUSED(logger);
#endif
std::vector<SoundDevice::Info> devices;
for(PaDeviceIndex dev = 0; dev < Pa_GetDeviceCount(); ++dev)
{
if(!Pa_GetDeviceInfo(dev))
{
continue;
}
if(Pa_GetDeviceInfo(dev)->hostApi < 0)
{
continue;
}
if(!Pa_GetHostApiInfo(Pa_GetDeviceInfo(dev)->hostApi))
{
continue;
}
if(!Pa_GetDeviceInfo(dev)->name)
{
continue;
}
if(!Pa_GetHostApiInfo(Pa_GetDeviceInfo(dev)->hostApi)->name)
{
continue;
}
if(Pa_GetDeviceInfo(dev)->maxOutputChannels <= 0)
{
continue;
}
SoundDevice::Info result = SoundDevice::Info();
switch((Pa_GetHostApiInfo(Pa_GetDeviceInfo(dev)->hostApi)->type))
{
case paWASAPI:
result.type = TypePORTAUDIO_WASAPI;
break;
case paWDMKS:
result.type = TypePORTAUDIO_WDMKS;
break;
case paMME:
result.type = TypePORTAUDIO_WMME;
break;
case paDirectSound:
result.type = TypePORTAUDIO_DS;
break;
default:
result.type = MPT_USTRING("PortAudio") + MPT_USTRING("-") + mpt::format<mpt::ustring>::dec(static_cast<int>(Pa_GetHostApiInfo(Pa_GetDeviceInfo(dev)->hostApi)->type));
break;
}
result.internalID = mpt::format<mpt::ustring>::dec(dev);
result.name = mpt::transcode<mpt::ustring>(mpt::common_encoding::utf8, Pa_GetDeviceInfo(dev)->name);
result.apiName = mpt::transcode<mpt::ustring>(mpt::common_encoding::utf8, Pa_GetHostApiInfo(Pa_GetDeviceInfo(dev)->hostApi)->name);
result.extraData[MPT_USTRING("PortAudio-HostAPI-name")] = mpt::transcode<mpt::ustring>(mpt::common_encoding::utf8, Pa_GetHostApiInfo(Pa_GetDeviceInfo(dev)->hostApi)->name);
result.apiPath.push_back(MPT_USTRING("PortAudio"));
result.useNameAsIdentifier = true;
result.flags = {
Info::Usability::Unknown,
Info::Level::Unknown,
Info::Compatible::Unknown,
Info::Api::Unknown,
Info::Io::Unknown,
Info::Mixing::Unknown,
Info::Implementor::External};
// clang-format off
switch(Pa_GetHostApiInfo(Pa_GetDeviceInfo(dev)->hostApi)->type)
{
case paDirectSound:
result.apiName = MPT_USTRING("DirectSound");
result.default_ = ((Pa_GetHostApiInfo(Pa_GetDeviceInfo(dev)->hostApi)->defaultOutputDevice == static_cast<PaDeviceIndex>(dev)) ? Info::Default::Managed : Info::Default::None);
result.flags = {
sysInfo.SystemClass == mpt::osinfo::osclass::Windows ? sysInfo.IsWindowsOriginal() && sysInfo.WindowsVersion.IsBefore(mpt::osinfo::windows::Version::Win7) ? Info::Usability::Usable : Info::Usability::Deprecated : Info::Usability::NotAvailable,
Info::Level::Secondary,
Info::Compatible::No,
sysInfo.SystemClass == mpt::osinfo::osclass::Windows ? sysInfo.IsWindowsWine() ? Info::Api::Emulated : sysInfo.WindowsVersion.IsAtLeast(mpt::osinfo::windows::Version::WinVista) ? Info::Api::Emulated : Info::Api::Native : Info::Api::Emulated,
Info::Io::FullDuplex,
Info::Mixing::Software,
Info::Implementor::External
};
break;
case paMME:
result.apiName = MPT_USTRING("MME");
result.default_ = ((Pa_GetHostApiInfo(Pa_GetDeviceInfo(dev)->hostApi)->defaultOutputDevice == static_cast<PaDeviceIndex>(dev)) ? Info::Default::Named : Info::Default::None);
result.flags = {
sysInfo.SystemClass == mpt::osinfo::osclass::Windows ? sysInfo.IsWindowsOriginal() && sysInfo.WindowsVersion.IsBefore(mpt::osinfo::windows::Version::Win7) ? Info::Usability::Usable : Info::Usability::Legacy : Info::Usability::NotAvailable,
Info::Level::Secondary,
Info::Compatible::No,
sysInfo.SystemClass == mpt::osinfo::osclass::Windows ? sysInfo.IsWindowsWine() ? Info::Api::Emulated : sysInfo.WindowsVersion.IsAtLeast(mpt::osinfo::windows::Version::WinVista) ? Info::Api::Emulated : Info::Api::Native : Info::Api::Emulated,
Info::Io::FullDuplex,
Info::Mixing::Software,
Info::Implementor::External
};
break;
case paASIO:
result.apiName = MPT_USTRING("ASIO");
result.default_ = ((Pa_GetHostApiInfo(Pa_GetDeviceInfo(dev)->hostApi)->defaultOutputDevice == static_cast<PaDeviceIndex>(dev)) ? Info::Default::Named : Info::Default::None);
result.flags = {
sysInfo.SystemClass == mpt::osinfo::osclass::Windows ? sysInfo.IsWindowsOriginal() ? Info::Usability::Usable : Info::Usability::Experimental : Info::Usability::NotAvailable,
Info::Level::Secondary,
Info::Compatible::No,
sysInfo.SystemClass == mpt::osinfo::osclass::Windows && sysInfo.IsWindowsOriginal() ? Info::Api::Native : Info::Api::Emulated,
Info::Io::FullDuplex,
Info::Mixing::Hardware,
Info::Implementor::External
};
break;
case paCoreAudio:
result.apiName = MPT_USTRING("CoreAudio");
result.default_ = ((Pa_GetHostApiInfo(Pa_GetDeviceInfo(dev)->hostApi)->defaultOutputDevice == static_cast<PaDeviceIndex>(dev)) ? Info::Default::Named : Info::Default::None);
result.flags = {
sysInfo.SystemClass == mpt::osinfo::osclass::Darwin ? Info::Usability::Usable : Info::Usability::NotAvailable,
Info::Level::Secondary,
Info::Compatible::Yes,
sysInfo.SystemClass == mpt::osinfo::osclass::Darwin ? Info::Api::Native : Info::Api::Emulated,
Info::Io::FullDuplex,
Info::Mixing::Server,
Info::Implementor::External
};
break;
case paOSS:
result.apiName = MPT_USTRING("OSS");
result.default_ = ((Pa_GetHostApiInfo(Pa_GetDeviceInfo(dev)->hostApi)->defaultOutputDevice == static_cast<PaDeviceIndex>(dev)) ? Info::Default::Named : Info::Default::None);
result.flags = {
sysInfo.SystemClass == mpt::osinfo::osclass::BSD ? Info::Usability::Usable : sysInfo.SystemClass == mpt::osinfo::osclass::Linux ? Info::Usability::Deprecated : Info::Usability::NotAvailable,
Info::Level::Primary,
Info::Compatible::No,
sysInfo.SystemClass == mpt::osinfo::osclass::BSD ? Info::Api::Native : sysInfo.SystemClass == mpt::osinfo::osclass::Linux ? Info::Api::Emulated : Info::Api::Emulated,
Info::Io::FullDuplex,
sysInfo.SystemClass == mpt::osinfo::osclass::BSD ? Info::Mixing::Hardware : sysInfo.SystemClass == mpt::osinfo::osclass::Linux ? Info::Mixing::Software : Info::Mixing::Software,
Info::Implementor::External
};
break;
case paALSA:
result.apiName = MPT_USTRING("ALSA");
result.default_ = ((Pa_GetHostApiInfo(Pa_GetDeviceInfo(dev)->hostApi)->defaultOutputDevice == static_cast<PaDeviceIndex>(dev)) ? Info::Default::Named : Info::Default::None);
result.flags = {
sysInfo.SystemClass == mpt::osinfo::osclass::Linux ? Info::Usability::Usable : Info::Usability::Experimental,
Info::Level::Primary,
Info::Compatible::No,
sysInfo.SystemClass == mpt::osinfo::osclass::Linux ? Info::Api::Native : Info::Api::Emulated,
Info::Io::FullDuplex,
sysInfo.SystemClass == mpt::osinfo::osclass::Linux ? Info::Mixing::Hardware : Info::Mixing::Software,
Info::Implementor::External
};
break;
case paAL:
result.apiName = MPT_USTRING("OpenAL");
result.default_ = ((Pa_GetHostApiInfo(Pa_GetDeviceInfo(dev)->hostApi)->defaultOutputDevice == static_cast<PaDeviceIndex>(dev)) ? Info::Default::Named : Info::Default::None);
result.flags = {
Info::Usability::Usable,
Info::Level::Primary,
Info::Compatible::No,
sysInfo.SystemClass == mpt::osinfo::osclass::Windows && sysInfo.IsWindowsOriginal() && sysInfo.WindowsVersion.IsBefore(mpt::osinfo::windows::Version::WinVista) ? Info::Api::Native : Info::Api::Emulated,
Info::Io::FullDuplex,
sysInfo.SystemClass == mpt::osinfo::osclass::Windows && sysInfo.IsWindowsOriginal() && sysInfo.WindowsVersion.IsBefore(mpt::osinfo::windows::Version::WinVista) ? Info::Mixing::Hardware : Info::Mixing::Software,
Info::Implementor::External
};
break;
case paWDMKS:
result.apiName = sysInfo.WindowsVersion.IsAtLeast(mpt::osinfo::windows::Version::WinVista) ? MPT_USTRING("WaveRT") : MPT_USTRING("WDM-KS");
result.default_ = ((Pa_GetHostApiInfo(Pa_GetDeviceInfo(dev)->hostApi)->defaultOutputDevice == static_cast<PaDeviceIndex>(dev)) ? Info::Default::Named : Info::Default::None);
result.flags = {
sysInfo.SystemClass == mpt::osinfo::osclass::Windows ? sysInfo.IsWindowsOriginal() ? sysInfo.WindowsVersion.IsBefore(mpt::osinfo::windows::Version::WinVista) ? Info::Usability::Usable : Info::Usability::Usable : Info::Usability::Broken : Info::Usability::NotAvailable,
Info::Level::Primary,
Info::Compatible::No,
sysInfo.SystemClass == mpt::osinfo::osclass::Windows ? sysInfo.IsWindowsOriginal() ? sysInfo.WindowsVersion.IsBefore(mpt::osinfo::windows::Version::WinVista) ? Info::Api::Native : Info::Api::Native : Info::Api::Emulated : Info::Api::Emulated,
Info::Io::FullDuplex,
Info::Mixing::Hardware,
Info::Implementor::External
};
break;
case paJACK:
result.apiName = MPT_USTRING("JACK");
result.default_ = ((Pa_GetHostApiInfo(Pa_GetDeviceInfo(dev)->hostApi)->defaultOutputDevice == static_cast<PaDeviceIndex>(dev)) ? Info::Default::Managed : Info::Default::None);
result.flags = {
sysInfo.SystemClass == mpt::osinfo::osclass::Linux ? Info::Usability::Usable : sysInfo.SystemClass == mpt::osinfo::osclass::Darwin ? Info::Usability::Usable : Info::Usability::Experimental,
Info::Level::Secondary,
Info::Compatible::Yes,
sysInfo.SystemClass == mpt::osinfo::osclass::Linux ? Info::Api::Native : Info::Api::Emulated,
Info::Io::FullDuplex,
Info::Mixing::Server,
Info::Implementor::External
};
break;
case paWASAPI:
result.apiName = MPT_USTRING("WASAPI");
result.default_ = ((Pa_GetHostApiInfo(Pa_GetDeviceInfo(dev)->hostApi)->defaultOutputDevice == static_cast<PaDeviceIndex>(dev)) ? Info::Default::Named : Info::Default::None);
result.flags = {
sysInfo.SystemClass == mpt::osinfo::osclass::Windows ?
sysInfo.IsWindowsOriginal() ?
sysInfo.WindowsVersion.IsAtLeast(mpt::osinfo::windows::Version::Win7) ?
Info::Usability::Usable
:
sysInfo.WindowsVersion.IsAtLeast(mpt::osinfo::windows::Version::WinVista) ?
Info::Usability::Experimental
:
Info::Usability::NotAvailable
:
Info::Usability::Usable
:
Info::Usability::NotAvailable,
Info::Level::Primary,
Info::Compatible::No,
sysInfo.SystemClass == mpt::osinfo::osclass::Windows ? Info::Api::Native : Info::Api::Emulated,
Info::Io::FullDuplex,
Info::Mixing::Server,
Info::Implementor::External
};
break;
default:
// nothing
break;
}
// clang-format on
PALOG(MPT_UFORMAT_MESSAGE("PortAudio: {}, {}, {}, {}")(result.internalID, result.name, result.apiName, static_cast<int>(result.default_)));
PALOG(MPT_UFORMAT_MESSAGE(" low : {}")(Pa_GetDeviceInfo(dev)->defaultLowOutputLatency));
PALOG(MPT_UFORMAT_MESSAGE(" high : {}")(Pa_GetDeviceInfo(dev)->defaultHighOutputLatency));
if((result.default_ != Info::Default::None) && (Pa_GetHostApiInfo(Pa_GetDeviceInfo(dev)->hostApi)->type == paWASAPI))
{
auto defaultResult = result;
defaultResult.default_ = Info::Default::Managed;
defaultResult.name = MPT_USTRING("Default Device");
defaultResult.internalID = MPT_USTRING("WASAPI-Default");
defaultResult.useNameAsIdentifier = false;
devices.push_back(defaultResult);
result.default_ = Info::Default::Named;
}
devices.push_back(result);
}
return devices;
}
std::vector<std::pair<PaDeviceIndex, mpt::ustring>> CPortaudioDevice::EnumerateInputOnlyDevices(PaHostApiTypeId hostApiType)
{
std::vector<std::pair<PaDeviceIndex, mpt::ustring>> result;
for(PaDeviceIndex dev = 0; dev < Pa_GetDeviceCount(); ++dev)
{
if(!Pa_GetDeviceInfo(dev))
{
continue;
}
if(Pa_GetDeviceInfo(dev)->hostApi < 0)
{
continue;
}
if(!Pa_GetHostApiInfo(Pa_GetDeviceInfo(dev)->hostApi))
{
continue;
}
if(!Pa_GetDeviceInfo(dev)->name)
{
continue;
}
if(!Pa_GetHostApiInfo(Pa_GetDeviceInfo(dev)->hostApi)->name)
{
continue;
}
if(Pa_GetDeviceInfo(dev)->maxInputChannels <= 0)
{
continue;
}
if(Pa_GetDeviceInfo(dev)->maxOutputChannels > 0)
{ // only find devices with only input channels
continue;
}
if(Pa_GetHostApiInfo(Pa_GetDeviceInfo(dev)->hostApi)->type != hostApiType)
{
continue;
}
result.push_back(std::make_pair(dev, mpt::transcode<mpt::ustring>(mpt::common_encoding::utf8, Pa_GetDeviceInfo(dev)->name)));
}
return result;
}
bool CPortaudioDevice::HasInputChannelsOnSameDevice() const
{
if(m_DeviceIndex == paNoDevice)
{
return false;
}
const PaDeviceInfo *deviceinfo = Pa_GetDeviceInfo(m_DeviceIndex);
if(!deviceinfo)
{
return false;
}
return (deviceinfo->maxInputChannels > 0);
}
#if MPT_COMPILER_MSVC
static void PortaudioLog(const char *text)
{
if(!text)
{
return;
}
#if PA_LOG_ENABLED
MPT_LOG(mpt::log::GlobalLogger(), LogDebug, "PortAudio", MPT_UFORMAT_MESSAGE("PortAudio: {}")(mpt::transcode<mpt::ustring>(mpt::common_encoding::utf8, text)));
#endif
}
#endif // MPT_COMPILER_MSVC
PortAudioInitializer::PortAudioInitializer()
{
#if defined(MODPLUG_TRACKER) && MPT_COMPILER_MSVC
PaUtil_SetDebugPrintFunction(PortaudioLog);
#endif
m_initialized = (Pa_Initialize() == paNoError);
}
void PortAudioInitializer::Reload()
{
if(m_initialized)
{
Pa_Terminate();
m_initialized = false;
}
m_initialized = (Pa_Initialize() == paNoError);
}
PortAudioInitializer::~PortAudioInitializer()
{
if(!m_initialized)
{
return;
}
Pa_Terminate();
}
#endif // MPT_WITH_PORTAUDIO
} // namespace SoundDevice
OPENMPT_NAMESPACE_END