123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380 |
- // Copyright 2008 Dolphin Emulator Project
- // SPDX-License-Identifier: GPL-2.0-or-later
- #ifdef _WIN32
- #include "AudioCommon/OpenALStream.h"
- #include <windows.h>
- #include <climits>
- #include <cstring>
- #include <thread>
- #include "Common/Logging/Log.h"
- #include "Common/MsgHandler.h"
- #include "Common/Thread.h"
- #include "Core/Config/MainSettings.h"
- static HMODULE s_openal_dll = nullptr;
- #define OPENAL_API_VISIT(X) \
- X(alBufferData) \
- X(alcCloseDevice) \
- X(alcCreateContext) \
- X(alcDestroyContext) \
- X(alcGetContextsDevice) \
- X(alcGetCurrentContext) \
- X(alcGetString) \
- X(alcIsExtensionPresent) \
- X(alcMakeContextCurrent) \
- X(alcOpenDevice) \
- X(alDeleteBuffers) \
- X(alDeleteSources) \
- X(alGenBuffers) \
- X(alGenSources) \
- X(alGetError) \
- X(alGetSourcei) \
- X(alGetString) \
- X(alIsExtensionPresent) \
- X(alSourcef) \
- X(alSourcei) \
- X(alSourcePlay) \
- X(alSourceQueueBuffers) \
- X(alSourceStop) \
- X(alSourceUnqueueBuffers)
- // Create func_t function pointer type and declare a nullptr-initialized static variable of that
- // type named "pfunc".
- #define DYN_FUNC_DECLARE(func) \
- typedef decltype(&func) func##_t; \
- static func##_t p##func = nullptr;
- // Attempt to load the function from the given module handle.
- #define OPENAL_FUNC_LOAD(func) \
- p##func = (func##_t)::GetProcAddress(s_openal_dll, #func); \
- if (!p##func) \
- { \
- return false; \
- }
- OPENAL_API_VISIT(DYN_FUNC_DECLARE);
- static bool InitFunctions()
- {
- OPENAL_API_VISIT(OPENAL_FUNC_LOAD);
- return true;
- }
- static bool InitLibrary()
- {
- if (s_openal_dll)
- return true;
- s_openal_dll = ::LoadLibrary(TEXT("openal32.dll"));
- if (!s_openal_dll)
- return false;
- if (!InitFunctions())
- {
- ::FreeLibrary(s_openal_dll);
- s_openal_dll = nullptr;
- return false;
- }
- return true;
- }
- bool OpenALStream::IsValid()
- {
- return InitLibrary();
- }
- //
- // AyuanX: Spec says OpenAL1.1 is thread safe already
- //
- bool OpenALStream::Init()
- {
- if (!palcIsExtensionPresent(nullptr, "ALC_ENUMERATION_EXT"))
- {
- PanicAlertFmtT("OpenAL: can't find sound devices");
- return false;
- }
- const char* default_device_dame = palcGetString(nullptr, ALC_DEFAULT_DEVICE_SPECIFIER);
- INFO_LOG_FMT(AUDIO, "Found OpenAL device {}", default_device_dame);
- ALCdevice* device = palcOpenDevice(default_device_dame);
- if (!device)
- {
- PanicAlertFmtT("OpenAL: can't open device {0}", default_device_dame);
- return false;
- }
- ALCcontext* context = palcCreateContext(device, nullptr);
- if (!context)
- {
- palcCloseDevice(device);
- PanicAlertFmtT("OpenAL: can't create context for device {0}", default_device_dame);
- return false;
- }
- palcMakeContextCurrent(context);
- m_run_thread.Set();
- m_thread = std::thread(&OpenALStream::SoundLoop, this);
- return true;
- }
- OpenALStream::~OpenALStream()
- {
- m_run_thread.Clear();
- m_thread.join();
- palSourceStop(m_source);
- palSourcei(m_source, AL_BUFFER, 0);
- // Clean up buffers and sources
- palDeleteSources(1, &m_source);
- m_source = 0;
- palDeleteBuffers(OAL_BUFFERS, m_buffers.data());
- ALCcontext* context = palcGetCurrentContext();
- ALCdevice* device = palcGetContextsDevice(context);
- palcMakeContextCurrent(nullptr);
- palcDestroyContext(context);
- palcCloseDevice(device);
- }
- void OpenALStream::SetVolume(int volume)
- {
- m_volume = (float)volume / 100.0f;
- if (m_source)
- palSourcef(m_source, AL_GAIN, m_volume);
- }
- bool OpenALStream::SetRunning(bool running)
- {
- if (running)
- {
- palSourcePlay(m_source);
- }
- else
- {
- palSourceStop(m_source);
- }
- return true;
- }
- static ALenum CheckALError(const char* desc)
- {
- ALenum err = palGetError();
- if (err != AL_NO_ERROR)
- {
- std::string type;
- switch (err)
- {
- case AL_INVALID_NAME:
- type = "AL_INVALID_NAME";
- break;
- case AL_INVALID_ENUM:
- type = "AL_INVALID_ENUM";
- break;
- case AL_INVALID_VALUE:
- type = "AL_INVALID_VALUE";
- break;
- case AL_INVALID_OPERATION:
- type = "AL_INVALID_OPERATION";
- break;
- case AL_OUT_OF_MEMORY:
- type = "AL_OUT_OF_MEMORY";
- break;
- default:
- type = "UNKNOWN_ERROR";
- break;
- }
- ERROR_LOG_FMT(AUDIO, "Error {}: {:08x} {}", desc, err, type);
- }
- return err;
- }
- static bool IsCreativeXFi()
- {
- return strstr(palGetString(AL_RENDERER), "X-Fi") != nullptr;
- }
- void OpenALStream::SoundLoop()
- {
- Common::SetCurrentThreadName("Audio thread - openal");
- bool float32_capable = palIsExtensionPresent("AL_EXT_float32") != 0;
- bool surround_capable = palIsExtensionPresent("AL_EXT_MCFORMATS") || IsCreativeXFi();
- bool use_surround = Config::ShouldUseDPL2Decoder() && surround_capable;
- // As there is no extension to check for 32-bit fixed point support
- // and we know that only a X-Fi with hardware OpenAL supports it,
- // we just check if one is being used.
- bool fixed32_capable = IsCreativeXFi();
- u32 frequency = m_mixer->GetSampleRate();
- u32 frames_per_buffer;
- // Can't have zero samples per buffer
- if (Config::Get(Config::MAIN_AUDIO_LATENCY) > 0)
- {
- frames_per_buffer = frequency / 1000 * Config::Get(Config::MAIN_AUDIO_LATENCY) / OAL_BUFFERS;
- }
- else
- {
- frames_per_buffer = frequency / 1000 * 1 / OAL_BUFFERS;
- }
- if (frames_per_buffer > OAL_MAX_FRAMES)
- {
- frames_per_buffer = OAL_MAX_FRAMES;
- }
- INFO_LOG_FMT(AUDIO, "Using {} buffers, each with {} audio frames for a total of {}.", OAL_BUFFERS,
- frames_per_buffer, frames_per_buffer * OAL_BUFFERS);
- // Should we make these larger just in case the mixer ever sends more samples
- // than what we request?
- m_realtime_buffer.resize(frames_per_buffer * STEREO_CHANNELS);
- m_source = 0;
- // Clear error state before querying or else we get false positives.
- ALenum err = palGetError();
- // Generate some AL Buffers for streaming
- palGenBuffers(OAL_BUFFERS, (ALuint*)m_buffers.data());
- err = CheckALError("generating buffers");
- // Generate a Source to playback the Buffers
- palGenSources(1, &m_source);
- err = CheckALError("generating sources");
- // Set the default sound volume as saved in the config file.
- palSourcef(m_source, AL_GAIN, m_volume);
- // TODO: Error handling
- // ALenum err = alGetError();
- unsigned int next_buffer = 0;
- unsigned int num_buffers_queued = 0;
- ALint state = 0;
- while (m_run_thread.IsSet())
- {
- // Block until we have a free buffer
- int num_buffers_processed;
- palGetSourcei(m_source, AL_BUFFERS_PROCESSED, &num_buffers_processed);
- if (num_buffers_queued == OAL_BUFFERS && !num_buffers_processed)
- {
- std::this_thread::sleep_for(std::chrono::milliseconds(1));
- continue;
- }
- // Remove the Buffer from the Queue.
- if (num_buffers_processed)
- {
- std::array<ALuint, OAL_BUFFERS> unqueued_buffer_ids;
- palSourceUnqueueBuffers(m_source, num_buffers_processed, unqueued_buffer_ids.data());
- err = CheckALError("unqueuing buffers");
- num_buffers_queued -= num_buffers_processed;
- }
- unsigned int min_frames = frames_per_buffer;
- if (use_surround)
- {
- std::array<float, OAL_MAX_FRAMES * SURROUND_CHANNELS> dpl2;
- u32 rendered_frames = m_mixer->MixSurround(dpl2.data(), min_frames);
- if (rendered_frames < min_frames)
- continue;
- if (float32_capable)
- {
- palBufferData(m_buffers[next_buffer], AL_FORMAT_51CHN32, dpl2.data(),
- rendered_frames * FRAME_SURROUND_FLOAT, frequency);
- }
- else if (fixed32_capable)
- {
- std::array<int, OAL_MAX_FRAMES * SURROUND_CHANNELS> surround_int32;
- for (u32 i = 0; i < rendered_frames * SURROUND_CHANNELS; ++i)
- {
- dpl2[i] = dpl2[i] * std::numeric_limits<int>::max();
- if (dpl2[i] > std::numeric_limits<int>::max())
- surround_int32[i] = std::numeric_limits<int>::max();
- else if (dpl2[i] < std::numeric_limits<int>::min())
- surround_int32[i] = std::numeric_limits<int>::min();
- else
- surround_int32[i] = static_cast<int>(dpl2[i]);
- }
- palBufferData(m_buffers[next_buffer], AL_FORMAT_51CHN32, surround_int32.data(),
- rendered_frames * FRAME_SURROUND_INT32, frequency);
- }
- else
- {
- std::array<short, OAL_MAX_FRAMES * SURROUND_CHANNELS> surround_short;
- for (u32 i = 0; i < rendered_frames * SURROUND_CHANNELS; ++i)
- {
- dpl2[i] = dpl2[i] * std::numeric_limits<short>::max();
- if (dpl2[i] > std::numeric_limits<short>::max())
- surround_short[i] = std::numeric_limits<short>::max();
- else if (dpl2[i] < std::numeric_limits<short>::min())
- surround_short[i] = std::numeric_limits<short>::min();
- else
- surround_short[i] = static_cast<short>(dpl2[i]);
- }
- palBufferData(m_buffers[next_buffer], AL_FORMAT_51CHN16, surround_short.data(),
- rendered_frames * FRAME_SURROUND_SHORT, frequency);
- }
- err = CheckALError("buffering data");
- if (err == AL_INVALID_ENUM)
- {
- // 5.1 is not supported by the host, fallback to stereo
- WARN_LOG_FMT(
- AUDIO, "Unable to set 5.1 surround mode. Updating OpenAL Soft might fix this issue.");
- use_surround = false;
- }
- }
- else
- {
- u32 rendered_frames = m_mixer->Mix(m_realtime_buffer.data(), min_frames);
- if (!rendered_frames)
- continue;
- palBufferData(m_buffers[next_buffer], AL_FORMAT_STEREO16, m_realtime_buffer.data(),
- rendered_frames * FRAME_STEREO_SHORT, frequency);
- }
- palSourceQueueBuffers(m_source, 1, &m_buffers[next_buffer]);
- err = CheckALError("queuing buffers");
- num_buffers_queued++;
- next_buffer = (next_buffer + 1) % OAL_BUFFERS;
- palGetSourcei(m_source, AL_SOURCE_STATE, &state);
- if (state != AL_PLAYING)
- {
- // Buffer underrun occurred, resume playback
- palSourcePlay(m_source);
- err = CheckALError("occurred resuming playback");
- }
- }
- }
- #endif // _WIN32
|