diff options
author | Chris Robinson <[email protected]> | 2021-03-08 22:29:40 -0800 |
---|---|---|
committer | Chris Robinson <[email protected]> | 2021-03-08 22:29:40 -0800 |
commit | 730c964029f7b649510490d8766aba801f576492 (patch) | |
tree | f9415b25e80d49ac40a02d6f94a96cfa355ca8b0 /alc/backends | |
parent | 0f7ed495e199f7158295c6a61ea0e0080a5b4339 (diff) |
Allow calling BackendBase::open multiple times on playback devices
It will not be called while the device is running. If the first call succeeds,
a subsequent call that happens to fail must leave the existing device state as
it was so it can be resumed.
This is a rough first pass. It will fail when trying to re-open the same device
which can only be opened once (for instance, with direct hardware access, on
hardware that doesn't do its own mixing). Some backends won't guarantee the new
device is usable until the reset() or start() call.
Diffstat (limited to 'alc/backends')
-rw-r--r-- | alc/backends/alsa.cpp | 8 | ||||
-rw-r--r-- | alc/backends/coreaudio.cpp | 12 | ||||
-rw-r--r-- | alc/backends/dsound.cpp | 138 | ||||
-rw-r--r-- | alc/backends/jack.cpp | 31 | ||||
-rw-r--r-- | alc/backends/oboe.cpp | 3 | ||||
-rw-r--r-- | alc/backends/opensl.cpp | 3 | ||||
-rw-r--r-- | alc/backends/oss.cpp | 10 | ||||
-rw-r--r-- | alc/backends/portaudio.cpp | 35 | ||||
-rw-r--r-- | alc/backends/pulseaudio.cpp | 17 | ||||
-rw-r--r-- | alc/backends/sdl2.cpp | 41 | ||||
-rw-r--r-- | alc/backends/sndio.cpp | 8 | ||||
-rw-r--r-- | alc/backends/solaris.cpp | 8 | ||||
-rw-r--r-- | alc/backends/wasapi.cpp | 262 | ||||
-rw-r--r-- | alc/backends/wave.cpp | 9 | ||||
-rw-r--r-- | alc/backends/winmm.cpp | 32 |
15 files changed, 360 insertions, 257 deletions
diff --git a/alc/backends/alsa.cpp b/alc/backends/alsa.cpp index bdfdd040..6a7a1187 100644 --- a/alc/backends/alsa.cpp +++ b/alc/backends/alsa.cpp @@ -635,12 +635,16 @@ void AlsaPlayback::open(const char *name) name = alsaDevice; driver = GetConfigValue(nullptr, "alsa", "device", "default"); } - TRACE("Opening device \"%s\"\n", driver); - int err{snd_pcm_open(&mPcmHandle, driver, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)}; + + snd_pcm_t *pcmHandle{}; + int err{snd_pcm_open(&pcmHandle, driver, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)}; if(err < 0) throw al::backend_exception{al::backend_error::NoDevice, "Could not open ALSA device \"%s\"", driver}; + if(mPcmHandle) + snd_pcm_close(mPcmHandle); + mPcmHandle = pcmHandle; /* Free alsa's global config tree. Otherwise valgrind reports a ton of leaks. */ snd_config_update_free_global(); diff --git a/alc/backends/coreaudio.cpp b/alc/backends/coreaudio.cpp index 8e38a777..977bdc78 100644 --- a/alc/backends/coreaudio.cpp +++ b/alc/backends/coreaudio.cpp @@ -118,10 +118,20 @@ void CoreAudioPlayback::open(const char *name) if(comp == nullptr) throw al::backend_exception{al::backend_error::NoDevice, "Could not find audio component"}; - OSStatus err{AudioComponentInstanceNew(comp, &mAudioUnit)}; + AudioUnit audioUnit{}; + OSStatus err{AudioComponentInstanceNew(comp, &audioUnit)}; if(err != noErr) throw al::backend_exception{al::backend_error::NoDevice, "Could not create component instance: %u", err}; + /* WARNING: I don't know if "valid" audio unit values are guaranteed to be + * non-0. If not, this logic is broken. + */ + if(mAudioUnit) + { + AudioUnitUninitialize(mAudioUnit); + AudioComponentInstanceDispose(mAudioUnit); + } + mAudioUnit = audioUnit; /* init and start the default audio unit... */ err = AudioUnitInitialize(mAudioUnit); diff --git a/alc/backends/dsound.cpp b/alc/backends/dsound.cpp index aa3a92a0..34fe25f4 100644 --- a/alc/backends/dsound.cpp +++ b/alc/backends/dsound.cpp @@ -107,6 +107,69 @@ HRESULT (WINAPI *pDirectSoundCaptureEnumerateW)(LPDSENUMCALLBACKW pDSEnumCallbac #endif +template<typename T> +class ComPtr { + T *mPtr{nullptr}; + +public: + ComPtr() noexcept = default; + ComPtr(const ComPtr &rhs) : mPtr{rhs.mPtr} { if(mPtr) mPtr->AddRef(); } + ComPtr(ComPtr&& rhs) noexcept : mPtr{rhs.mPtr} { rhs.mPtr = nullptr; } + ComPtr(std::nullptr_t) noexcept { } + explicit ComPtr(T *ptr) noexcept : mPtr{ptr} { } + ~ComPtr() { if(mPtr) mPtr->Release(); } + + ComPtr& operator=(const ComPtr &rhs) + { + if(!rhs.mPtr) + { + if(mPtr) + mPtr->Release(); + mPtr = nullptr; + } + else + { + rhs.mPtr->AddRef(); + try { + if(mPtr) + mPtr->Release(); + mPtr = rhs.mPtr; + } + catch(...) { + rhs.mPtr->Release(); + throw; + } + } + return *this; + } + ComPtr& operator=(ComPtr&& rhs) + { + if(mPtr) + mPtr->Release(); + mPtr = rhs.mPtr; + rhs.mPtr = nullptr; + return *this; + } + + operator bool() const noexcept { return mPtr != nullptr; } + + T& operator*() const noexcept { return *mPtr; } + T* operator->() const noexcept { return mPtr; } + T* get() const noexcept { return mPtr; } + T** getPtr() noexcept { return &mPtr; } + + T* release() noexcept + { + T *ret{mPtr}; + mPtr = nullptr; + return ret; + } + + void swap(ComPtr &rhs) noexcept { std::swap(mPtr, rhs.mPtr); } + void swap(ComPtr&& rhs) noexcept { std::swap(mPtr, rhs.mPtr); } +}; + + #define MONO SPEAKER_FRONT_CENTER #define STEREO (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT) #define QUAD (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT|SPEAKER_BACK_LEFT|SPEAKER_BACK_RIGHT) @@ -179,11 +242,11 @@ struct DSoundPlayback final : public BackendBase { void start() override; void stop() override; - IDirectSound *mDS{nullptr}; - IDirectSoundBuffer *mPrimaryBuffer{nullptr}; - IDirectSoundBuffer *mBuffer{nullptr}; - IDirectSoundNotify *mNotifies{nullptr}; - HANDLE mNotifyEvent{nullptr}; + ComPtr<IDirectSound> mDS; + ComPtr<IDirectSoundBuffer> mPrimaryBuffer; + ComPtr<IDirectSoundBuffer> mBuffer; + ComPtr<IDirectSoundNotify> mNotifies; + HANDLE mNotifyEvent{nullptr}; std::atomic<bool> mKillNow{true}; std::thread mThread; @@ -193,19 +256,11 @@ struct DSoundPlayback final : public BackendBase { DSoundPlayback::~DSoundPlayback() { - if(mNotifies) - mNotifies->Release(); mNotifies = nullptr; - if(mBuffer) - mBuffer->Release(); mBuffer = nullptr; - if(mPrimaryBuffer) - mPrimaryBuffer->Release(); mPrimaryBuffer = nullptr; - - if(mDS) - mDS->Release(); mDS = nullptr; + if(mNotifyEvent) CloseHandle(mNotifyEvent); mNotifyEvent = nullptr; @@ -234,8 +289,8 @@ FORCE_ALIGN int DSoundPlayback::mixerProc() bool Playing{false}; DWORD LastCursor{0u}; mBuffer->GetCurrentPosition(&LastCursor, nullptr); - while(!mKillNow.load(std::memory_order_acquire) && - mDevice->Connected.load(std::memory_order_acquire)) + while(!mKillNow.load(std::memory_order_acquire) + && mDevice->Connected.load(std::memory_order_acquire)) { // Get current play cursor DWORD PlayCursor; @@ -344,31 +399,34 @@ void DSoundPlayback::open(const char *name) } hr = DS_OK; - mNotifyEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr); - if(!mNotifyEvent) hr = E_FAIL; + if(!mNotifyEvent) + { + mNotifyEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr); + if(!mNotifyEvent) hr = E_FAIL; + } //DirectSound Init code + ComPtr<IDirectSound> ds; if(SUCCEEDED(hr)) - hr = DirectSoundCreate(guid, &mDS, nullptr); + hr = DirectSoundCreate(guid, ds.getPtr(), nullptr); if(SUCCEEDED(hr)) - hr = mDS->SetCooperativeLevel(GetForegroundWindow(), DSSCL_PRIORITY); + hr = ds->SetCooperativeLevel(GetForegroundWindow(), DSSCL_PRIORITY); if(FAILED(hr)) throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx", hr}; + mNotifies = nullptr; + mBuffer = nullptr; + mPrimaryBuffer = nullptr; + mDS = std::move(ds); + mDevice->DeviceName = name; } bool DSoundPlayback::reset() { - if(mNotifies) - mNotifies->Release(); mNotifies = nullptr; - if(mBuffer) - mBuffer->Release(); mBuffer = nullptr; - if(mPrimaryBuffer) - mPrimaryBuffer->Release(); mPrimaryBuffer = nullptr; switch(mDevice->FmtType) @@ -465,7 +523,7 @@ retry_open: DSBUFFERDESC DSBDescription{}; DSBDescription.dwSize = sizeof(DSBDescription); DSBDescription.dwFlags = DSBCAPS_PRIMARYBUFFER; - hr = mDS->CreateSoundBuffer(&DSBDescription, &mPrimaryBuffer, nullptr); + hr = mDS->CreateSoundBuffer(&DSBDescription, mPrimaryBuffer.getPtr(), nullptr); } if(SUCCEEDED(hr)) hr = mPrimaryBuffer->SetFormat(&OutputType.Format); @@ -485,7 +543,7 @@ retry_open: DSBDescription.dwBufferBytes = mDevice->BufferSize * OutputType.Format.nBlockAlign; DSBDescription.lpwfxFormat = &OutputType.Format; - hr = mDS->CreateSoundBuffer(&DSBDescription, &mBuffer, nullptr); + hr = mDS->CreateSoundBuffer(&DSBDescription, mBuffer.getPtr(), nullptr); if(FAILED(hr) && mDevice->FmtType == DevFmtFloat) { mDevice->FmtType = DevFmtShort; @@ -499,7 +557,7 @@ retry_open: hr = mBuffer->QueryInterface(IID_IDirectSoundNotify, &ptr); if(SUCCEEDED(hr)) { - mNotifies = static_cast<IDirectSoundNotify*>(ptr); + mNotifies = ComPtr<IDirectSoundNotify>{static_cast<IDirectSoundNotify*>(ptr)}; uint num_updates{mDevice->BufferSize / mDevice->UpdateSize}; assert(num_updates <= MAX_UPDATES); @@ -517,14 +575,8 @@ retry_open: if(FAILED(hr)) { - if(mNotifies) - mNotifies->Release(); mNotifies = nullptr; - if(mBuffer) - mBuffer->Release(); mBuffer = nullptr; - if(mPrimaryBuffer) - mPrimaryBuffer->Release(); mPrimaryBuffer = nullptr; return false; } @@ -567,8 +619,8 @@ struct DSoundCapture final : public BackendBase { void captureSamples(al::byte *buffer, uint samples) override; uint availableSamples() override; - IDirectSoundCapture *mDSC{nullptr}; - IDirectSoundCaptureBuffer *mDSCbuffer{nullptr}; + ComPtr<IDirectSoundCapture> mDSC; + ComPtr<IDirectSoundCaptureBuffer> mDSCbuffer; DWORD mBufferBytes{0u}; DWORD mCursor{0u}; @@ -582,12 +634,8 @@ DSoundCapture::~DSoundCapture() if(mDSCbuffer) { mDSCbuffer->Stop(); - mDSCbuffer->Release(); mDSCbuffer = nullptr; } - - if(mDSC) - mDSC->Release(); mDSC = nullptr; } @@ -693,20 +741,16 @@ void DSoundCapture::open(const char *name) DSCBDescription.lpwfxFormat = &InputType.Format; //DirectSoundCapture Init code - hr = DirectSoundCaptureCreate(guid, &mDSC, nullptr); + hr = DirectSoundCaptureCreate(guid, mDSC.getPtr(), nullptr); if(SUCCEEDED(hr)) - mDSC->CreateCaptureBuffer(&DSCBDescription, &mDSCbuffer, nullptr); + mDSC->CreateCaptureBuffer(&DSCBDescription, mDSCbuffer.getPtr(), nullptr); if(SUCCEEDED(hr)) mRing = RingBuffer::Create(mDevice->BufferSize, InputType.Format.nBlockAlign, false); if(FAILED(hr)) { mRing = nullptr; - if(mDSCbuffer) - mDSCbuffer->Release(); mDSCbuffer = nullptr; - if(mDSC) - mDSC->Release(); mDSC = nullptr; throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx", diff --git a/alc/backends/jack.cpp b/alc/backends/jack.cpp index 42509d77..cf52e7a4 100644 --- a/alc/backends/jack.cpp +++ b/alc/backends/jack.cpp @@ -384,22 +384,23 @@ int JackPlayback::mixerProc() void JackPlayback::open(const char *name) { - mPortPattern.clear(); - - const PathNamePair &binname = GetProcBinary(); - const char *client_name{binname.fname.empty() ? "alsoft" : binname.fname.c_str()}; - - jack_status_t status; - mClient = jack_client_open(client_name, ClientOptions, &status, nullptr); - if(mClient == nullptr) - throw al::backend_exception{al::backend_error::DeviceError, - "Failed to open client connection: 0x%02x", status}; - if((status&JackServerStarted)) - TRACE("JACK server started\n"); - if((status&JackNameNotUnique)) + if(!mClient) { - client_name = jack_get_client_name(mClient); - TRACE("Client name not unique, got '%s' instead\n", client_name); + const PathNamePair &binname = GetProcBinary(); + const char *client_name{binname.fname.empty() ? "alsoft" : binname.fname.c_str()}; + + jack_status_t status; + mClient = jack_client_open(client_name, ClientOptions, &status, nullptr); + if(mClient == nullptr) + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to open client connection: 0x%02x", status}; + if((status&JackServerStarted)) + TRACE("JACK server started\n"); + if((status&JackNameNotUnique)) + { + client_name = jack_get_client_name(mClient); + TRACE("Client name not unique, got '%s' instead\n", client_name); + } } if(PlaybackList.empty()) diff --git a/alc/backends/oboe.cpp b/alc/backends/oboe.cpp index d84cea8e..b94a0882 100644 --- a/alc/backends/oboe.cpp +++ b/alc/backends/oboe.cpp @@ -64,9 +64,10 @@ void OboePlayback::open(const char *name) name}; /* Open a basic output stream, just to ensure it can work. */ + oboe::ManagedStream stream; oboe::Result result{oboe::AudioStreamBuilder{}.setDirection(oboe::Direction::Output) ->setPerformanceMode(oboe::PerformanceMode::LowLatency) - ->openManagedStream(mStream)}; + ->openManagedStream(stream)}; if(result != oboe::Result::OK) throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: %s", oboe::convertToText(result)}; diff --git a/alc/backends/opensl.cpp b/alc/backends/opensl.cpp index 917e097f..cae17ce1 100644 --- a/alc/backends/opensl.cpp +++ b/alc/backends/opensl.cpp @@ -316,6 +316,9 @@ void OpenSLPlayback::open(const char *name) throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", name}; + /* There's only one device, so if it's already open, there's nothing to do. */ + if(mEngineObj) return; + // create engine SLresult result{slCreateEngine(&mEngineObj, 0, nullptr, 0, nullptr, nullptr)}; PRINTERR(result, "slCreateEngine"); diff --git a/alc/backends/oss.cpp b/alc/backends/oss.cpp index 4cff5959..196744b0 100644 --- a/alc/backends/oss.cpp +++ b/alc/backends/oss.cpp @@ -249,7 +249,7 @@ struct OSSPlayback final : public BackendBase { OSSPlayback::~OSSPlayback() { if(mFd != -1) - close(mFd); + ::close(mFd); mFd = -1; } @@ -328,11 +328,15 @@ void OSSPlayback::open(const char *name) devname = iter->device_name.c_str(); } - mFd = ::open(devname, O_WRONLY); - if(mFd == -1) + int fd{::open(devname, O_WRONLY)}; + if(fd == -1) throw al::backend_exception{al::backend_error::NoDevice, "Could not open %s: %s", devname, strerror(errno)}; + if(mFd != -1) + ::close(mFd); + mFd = fd; + mDevice->DeviceName = name; } diff --git a/alc/backends/portaudio.cpp b/alc/backends/portaudio.cpp index d2e6a5a4..188f72f2 100644 --- a/alc/backends/portaudio.cpp +++ b/alc/backends/portaudio.cpp @@ -123,53 +123,58 @@ void PortPlayback::open(const char *name) throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", name}; - mUpdateSize = mDevice->UpdateSize; - + PaStreamParameters params{}; auto devidopt = ConfigValueInt(nullptr, "port", "device"); - if(devidopt && *devidopt >= 0) mParams.device = *devidopt; - else mParams.device = Pa_GetDefaultOutputDevice(); - mParams.suggestedLatency = mDevice->BufferSize / static_cast<double>(mDevice->Frequency); - mParams.hostApiSpecificStreamInfo = nullptr; + if(devidopt && *devidopt >= 0) params.device = *devidopt; + else params.device = Pa_GetDefaultOutputDevice(); + params.suggestedLatency = mDevice->BufferSize / static_cast<double>(mDevice->Frequency); + params.hostApiSpecificStreamInfo = nullptr; - mParams.channelCount = ((mDevice->FmtChans == DevFmtMono) ? 1 : 2); + params.channelCount = ((mDevice->FmtChans == DevFmtMono) ? 1 : 2); switch(mDevice->FmtType) { case DevFmtByte: - mParams.sampleFormat = paInt8; + params.sampleFormat = paInt8; break; case DevFmtUByte: - mParams.sampleFormat = paUInt8; + params.sampleFormat = paUInt8; break; case DevFmtUShort: /* fall-through */ case DevFmtShort: - mParams.sampleFormat = paInt16; + params.sampleFormat = paInt16; break; case DevFmtUInt: /* fall-through */ case DevFmtInt: - mParams.sampleFormat = paInt32; + params.sampleFormat = paInt32; break; case DevFmtFloat: - mParams.sampleFormat = paFloat32; + params.sampleFormat = paFloat32; break; } retry_open: - PaError err{Pa_OpenStream(&mStream, nullptr, &mParams, mDevice->Frequency, mDevice->UpdateSize, + PaStream *stream{}; + PaError err{Pa_OpenStream(&stream, nullptr, ¶ms, mDevice->Frequency, mDevice->UpdateSize, paNoFlag, &PortPlayback::writeCallbackC, this)}; if(err != paNoError) { - if(mParams.sampleFormat == paFloat32) + if(params.sampleFormat == paFloat32) { - mParams.sampleFormat = paInt16; + params.sampleFormat = paInt16; goto retry_open; } throw al::backend_exception{al::backend_error::NoDevice, "Failed to open stream: %s", Pa_GetErrorText(err)}; } + Pa_CloseStream(mStream); + mStream = stream; + mParams = params; + mUpdateSize = mDevice->UpdateSize; + mDevice->DeviceName = name; } diff --git a/alc/backends/pulseaudio.cpp b/alc/backends/pulseaudio.cpp index a6aa93a5..90f56968 100644 --- a/alc/backends/pulseaudio.cpp +++ b/alc/backends/pulseaudio.cpp @@ -846,7 +846,8 @@ void PulsePlayback::open(const char *name) } auto plock = mMainloop.getUniqueLock(); - mContext = mMainloop.connectContext(plock); + if(!mContext) + mContext = mMainloop.connectContext(plock); pa_stream_flags_t flags{PA_STREAM_START_CORKED | PA_STREAM_FIX_FORMAT | PA_STREAM_FIX_RATE | PA_STREAM_FIX_CHANNELS}; @@ -864,8 +865,18 @@ void PulsePlayback::open(const char *name) if(defname) pulse_name = defname->c_str(); } TRACE("Connecting to \"%s\"\n", pulse_name ? pulse_name : "(default)"); - mStream = mMainloop.connectStream(pulse_name, plock, mContext, flags, nullptr, &spec, nullptr, - BackendType::Playback); + pa_stream *stream{mMainloop.connectStream(pulse_name, plock, mContext, flags, nullptr, &spec, + nullptr, BackendType::Playback)}; + if(mStream) + { + pa_stream_set_state_callback(mStream, nullptr, nullptr); + pa_stream_set_moved_callback(mStream, nullptr, nullptr); + pa_stream_set_write_callback(mStream, nullptr, nullptr); + pa_stream_set_buffer_attr_callback(mStream, nullptr, nullptr); + pa_stream_disconnect(mStream); + pa_stream_unref(mStream); + } + mStream = stream; pa_stream_set_moved_callback(mStream, &PulsePlayback::streamMovedCallbackC, this); mFrameSize = static_cast<uint>(pa_frame_size(pa_stream_get_sample_spec(mStream))); diff --git a/alc/backends/sdl2.cpp b/alc/backends/sdl2.cpp index 084b51c0..4c4c71ed 100644 --- a/alc/backends/sdl2.cpp +++ b/alc/backends/sdl2.cpp @@ -106,32 +106,34 @@ void Sdl2Backend::open(const char *name) /* Passing nullptr to SDL_OpenAudioDevice opens a default, which isn't * necessarily the first in the list. */ + SDL_AudioDeviceID devid; if(!name || strcmp(name, defaultDeviceName) == 0) - mDeviceID = SDL_OpenAudioDevice(nullptr, SDL_FALSE, &want, &have, - SDL_AUDIO_ALLOW_ANY_CHANGE); + devid = SDL_OpenAudioDevice(nullptr, SDL_FALSE, &want, &have, SDL_AUDIO_ALLOW_ANY_CHANGE); else { const size_t prefix_len = strlen(DEVNAME_PREFIX); if(strncmp(name, DEVNAME_PREFIX, prefix_len) == 0) - mDeviceID = SDL_OpenAudioDevice(name+prefix_len, SDL_FALSE, &want, &have, + devid = SDL_OpenAudioDevice(name+prefix_len, SDL_FALSE, &want, &have, SDL_AUDIO_ALLOW_ANY_CHANGE); else - mDeviceID = SDL_OpenAudioDevice(name, SDL_FALSE, &want, &have, - SDL_AUDIO_ALLOW_ANY_CHANGE); + devid = SDL_OpenAudioDevice(name, SDL_FALSE, &want, &have, SDL_AUDIO_ALLOW_ANY_CHANGE); } - if(mDeviceID == 0) + if(!devid) throw al::backend_exception{al::backend_error::NoDevice, "%s", SDL_GetError()}; - mDevice->Frequency = static_cast<uint>(have.freq); - + DevFmtChannels devchans{}; if(have.channels == 1) - mDevice->FmtChans = DevFmtMono; + devchans = DevFmtMono; else if(have.channels == 2) - mDevice->FmtChans = DevFmtStereo; + devchans = DevFmtStereo; else + { + SDL_CloseAudioDevice(devid); throw al::backend_exception{al::backend_error::DeviceError, "Unhandled SDL channel count: %d", int{have.channels}}; + } + DevFmtType devtype{}; switch(have.format) { case AUDIO_U8: mDevice->FmtType = DevFmtUByte; break; @@ -141,17 +143,20 @@ void Sdl2Backend::open(const char *name) case AUDIO_S32SYS: mDevice->FmtType = DevFmtInt; break; case AUDIO_F32SYS: mDevice->FmtType = DevFmtFloat; break; default: + SDL_CloseAudioDevice(devid); throw al::backend_exception{al::backend_error::DeviceError, "Unhandled SDL format: 0x%04x", have.format}; } - mDevice->UpdateSize = have.samples; - mDevice->BufferSize = have.samples * 2; /* SDL always (tries to) use two periods. */ - mFrameSize = mDevice->frameSizeFromFmt(); - mFrequency = mDevice->Frequency; - mFmtChans = mDevice->FmtChans; - mFmtType = mDevice->FmtType; - mUpdateSize = mDevice->UpdateSize; + if(mDeviceID) + SDL_CloseAudioDevice(mDeviceID); + mDeviceID = devid; + + mFrameSize = FrameSizeFromDevFmt(devchans, devtype, 0); + mFrequency = static_cast<uint>(have.freq); + mFmtChans = devchans; + mFmtType = devtype; + mUpdateSize = have.samples; mDevice->DeviceName = name ? name : defaultDeviceName; } @@ -162,7 +167,7 @@ bool Sdl2Backend::reset() mDevice->FmtChans = mFmtChans; mDevice->FmtType = mFmtType; mDevice->UpdateSize = mUpdateSize; - mDevice->BufferSize = mUpdateSize * 2; + mDevice->BufferSize = mUpdateSize * 2; /* SDL always (tries to) use two periods. */ setDefaultWFXChannelOrder(); return true; } diff --git a/alc/backends/sndio.cpp b/alc/backends/sndio.cpp index 41bdb73b..a26eeaa6 100644 --- a/alc/backends/sndio.cpp +++ b/alc/backends/sndio.cpp @@ -122,10 +122,14 @@ void SndioPlayback::open(const char *name) throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", name}; - mSndHandle = sio_open(nullptr, SIO_PLAY, 0); - if(mSndHandle == nullptr) + sio_hdl *sndHandle{sio_open(nullptr, SIO_PLAY, 0)}; + if(!sndHandle) throw al::backend_exception{al::backend_error::NoDevice, "Could not open backend device"}; + if(mSndHandle) + sio_close(mSndHandle); + mSndHandle = sndHandle; + mDevice->DeviceName = name; } diff --git a/alc/backends/solaris.cpp b/alc/backends/solaris.cpp index a5bb45b0..68ab814c 100644 --- a/alc/backends/solaris.cpp +++ b/alc/backends/solaris.cpp @@ -148,11 +148,15 @@ void SolarisBackend::open(const char *name) throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", name}; - mFd = ::open(solaris_driver.c_str(), O_WRONLY); - if(mFd == -1) + int fd{::open(solaris_driver.c_str(), O_WRONLY)}; + if(fd == -1) throw al::backend_exception{al::backend_error::NoDevice, "Could not open %s: %s", solaris_driver.c_str(), strerror(errno)}; + if(mFd != -1) + ::close(mFd); + mFd = fd; + mDevice->DeviceName = name; } diff --git a/alc/backends/wasapi.cpp b/alc/backends/wasapi.cpp index 0786a7d7..b594aebe 100644 --- a/alc/backends/wasapi.cpp +++ b/alc/backends/wasapi.cpp @@ -238,10 +238,9 @@ struct DevMap { bool checkName(const al::vector<DevMap> &list, const std::string &name) { - return std::find_if(list.cbegin(), list.cend(), - [&name](const DevMap &entry) -> bool - { return entry.name == name; } - ) != list.cend(); + auto match_name = [&name](const DevMap &entry) -> bool + { return entry.name == name; }; + return std::find_if(list.cbegin(), list.cend(), match_name) != list.cend(); } al::vector<DevMap> PlaybackDevices; @@ -297,26 +296,26 @@ NameGUIDPair get_device_name_and_guid(IMMDevice *device) return std::make_pair(std::move(name), std::move(guid)); } -void get_device_formfactor(IMMDevice *device, EndpointFormFactor *formfactor) +EndpointFormFactor get_device_formfactor(IMMDevice *device) { ComPtr<IPropertyStore> ps; - HRESULT hr = device->OpenPropertyStore(STGM_READ, ps.getPtr()); + HRESULT hr{device->OpenPropertyStore(STGM_READ, ps.getPtr())}; if(FAILED(hr)) { WARN("OpenPropertyStore failed: 0x%08lx\n", hr); - return; + return UnknownFormFactor; } + EndpointFormFactor formfactor{UnknownFormFactor}; PropVariant pvform; - hr = ps->GetValue(reinterpret_cast<const PROPERTYKEY&>(PKEY_AudioEndpoint_FormFactor), pvform.get()); + hr = ps->GetValue(PKEY_AudioEndpoint_FormFactor, pvform.get()); if(FAILED(hr)) WARN("GetValue AudioEndpoint_FormFactor failed: 0x%08lx\n", hr); else if(pvform->vt == VT_UI4) - *formfactor = static_cast<EndpointFormFactor>(pvform->ulVal); - else if(pvform->vt == VT_EMPTY) - *formfactor = UnknownFormFactor; - else + formfactor = static_cast<EndpointFormFactor>(pvform->ulVal); + else if(pvform->vt != VT_EMPTY) WARN("Unexpected PROPVARIANT type: 0x%04x\n", pvform->vt); + return formfactor; } @@ -484,6 +483,7 @@ void TraceFormat(const char *msg, const WAVEFORMATEX *format) enum class MsgType { OpenDevice, + ReopenDevice, ResetDevice, StartDevice, StopDevice, @@ -497,6 +497,7 @@ enum class MsgType { constexpr char MessageStr[static_cast<size_t>(MsgType::Count)][20]{ "Open Device", + "Reopen Device", "Reset Device", "Start Device", "Stop Device", @@ -511,7 +512,7 @@ constexpr char MessageStr[static_cast<size_t>(MsgType::Count)][20]{ struct WasapiProxy { virtual ~WasapiProxy() = default; - virtual HRESULT openProxy() = 0; + virtual HRESULT openProxy(const char *name) = 0; virtual void closeProxy() = 0; virtual HRESULT resetProxy() = 0; @@ -521,19 +522,20 @@ struct WasapiProxy { struct Msg { MsgType mType; WasapiProxy *mProxy; + const char *mParam; std::promise<HRESULT> mPromise; }; static std::deque<Msg> mMsgQueue; static std::mutex mMsgQueueLock; static std::condition_variable mMsgQueueCond; - std::future<HRESULT> pushMessage(MsgType type) + std::future<HRESULT> pushMessage(MsgType type, const char *param=nullptr) { std::promise<HRESULT> promise; std::future<HRESULT> future{promise.get_future()}; { std::lock_guard<std::mutex> _{mMsgQueueLock}; - mMsgQueue.emplace_back(Msg{type, this, std::move(promise)}); + mMsgQueue.emplace_back(Msg{type, this, param, std::move(promise)}); } mMsgQueueCond.notify_one(); return future; @@ -545,7 +547,7 @@ struct WasapiProxy { std::future<HRESULT> future{promise.get_future()}; { std::lock_guard<std::mutex> _{mMsgQueueLock}; - mMsgQueue.emplace_back(Msg{type, nullptr, std::move(promise)}); + mMsgQueue.emplace_back(Msg{type, nullptr, nullptr, std::move(promise)}); } mMsgQueueCond.notify_one(); return future; @@ -600,9 +602,9 @@ int WasapiProxy::messageHandler(std::promise<HRESULT> *promise) Msg msg; while(popMessage(msg)) { - TRACE("Got message \"%s\" (0x%04x, this=%p)\n", + TRACE("Got message \"%s\" (0x%04x, this=%p, param=%p)\n", MessageStr[static_cast<size_t>(msg.mType)], static_cast<uint>(msg.mType), - decltype(std::declval<void*>()){msg.mProxy}); + static_cast<void*>(msg.mProxy), static_cast<const void*>(msg.mParam)); switch(msg.mType) { @@ -611,7 +613,7 @@ int WasapiProxy::messageHandler(std::promise<HRESULT> *promise) if(++deviceCount == 1) hr = cohr = CoInitializeEx(nullptr, COINIT_MULTITHREADED); if(SUCCEEDED(hr)) - hr = msg.mProxy->openProxy(); + hr = msg.mProxy->openProxy(msg.mParam); msg.mPromise.set_value(hr); if(FAILED(hr)) @@ -621,6 +623,11 @@ int WasapiProxy::messageHandler(std::promise<HRESULT> *promise) } continue; + case MsgType::ReopenDevice: + hr = msg.mProxy->openProxy(msg.mParam); + msg.mPromise.set_value(hr); + continue; + case MsgType::ResetDevice: hr = msg.mProxy->resetProxy(); msg.mPromise.set_value(hr); @@ -688,7 +695,7 @@ struct WasapiPlayback final : public BackendBase, WasapiProxy { int mixerProc(); void open(const char *name) override; - HRESULT openProxy() override; + HRESULT openProxy(const char *name) override; void closeProxy() override; bool reset() override; @@ -700,8 +707,6 @@ struct WasapiPlayback final : public BackendBase, WasapiProxy { ClockLatency getClockLatency() override; - std::wstring mDevId; - HRESULT mOpenStatus{E_FAIL}; ComPtr<IMMDevice> mMMDev{nullptr}; ComPtr<IAudioClient> mClient{nullptr}; @@ -796,83 +801,86 @@ void WasapiPlayback::open(const char *name) { HRESULT hr{S_OK}; - mNotifyEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr); - if(mNotifyEvent == nullptr) + if(!mNotifyEvent) { - ERR("Failed to create notify events: %lu\n", GetLastError()); - hr = E_FAIL; - } - - if(SUCCEEDED(hr)) - { - if(name) + mNotifyEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr); + if(mNotifyEvent == nullptr) { - if(PlaybackDevices.empty()) - pushMessage(MsgType::EnumeratePlayback).wait(); - + ERR("Failed to create notify events: %lu\n", GetLastError()); hr = E_FAIL; - auto iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(), - [name](const DevMap &entry) -> bool - { return entry.name == name || entry.endpoint_guid == name; }); - if(iter == PlaybackDevices.cend()) - { - const std::wstring wname{utf8_to_wstr(name)}; - iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(), - [&wname](const DevMap &entry) -> bool - { return entry.devid == wname; }); - } - if(iter == PlaybackDevices.cend()) - WARN("Failed to find device name matching \"%s\"\n", name); - else - { - mDevId = iter->devid; - mDevice->DeviceName = iter->name; - hr = S_OK; - } } } if(SUCCEEDED(hr)) - hr = pushMessage(MsgType::OpenDevice).get(); - mOpenStatus = hr; - - if(FAILED(hr)) { - if(mNotifyEvent != nullptr) - CloseHandle(mNotifyEvent); - mNotifyEvent = nullptr; + if(name && PlaybackDevices.empty()) + pushMessage(MsgType::EnumeratePlayback).wait(); - mDevId.clear(); + if(SUCCEEDED(mOpenStatus)) + hr = pushMessage(MsgType::ReopenDevice, name).get(); + else + { + hr = pushMessage(MsgType::OpenDevice, name).get(); + mOpenStatus = hr; + } + } + if(FAILED(hr)) throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx", hr}; - } } -HRESULT WasapiPlayback::openProxy() +HRESULT WasapiPlayback::openProxy(const char *name) { + const wchar_t *devid{nullptr}; + if(name) + { + auto iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(), + [name](const DevMap &entry) -> bool + { return entry.name == name || entry.endpoint_guid == name; }); + if(iter == PlaybackDevices.cend()) + { + const std::wstring wname{utf8_to_wstr(name)}; + iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(), + [&wname](const DevMap &entry) -> bool + { return entry.devid == wname; }); + } + if(iter == PlaybackDevices.cend()) + { + WARN("Failed to find device name matching \"%s\"\n", name); + return E_FAIL; + } + name = iter->name.c_str(); + devid = iter->devid.c_str(); + } + void *ptr; HRESULT hr{CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER, IID_IMMDeviceEnumerator, &ptr)}; - if(SUCCEEDED(hr)) + ComPtr<IMMDeviceEnumerator> enumerator{static_cast<IMMDeviceEnumerator*>(ptr)}; + ComPtr<IMMDevice> mmdev; + if(!devid) + hr = enumerator->GetDefaultAudioEndpoint(eRender, eMultimedia, mmdev.getPtr()); + else + hr = enumerator->GetDevice(devid, mmdev.getPtr()); + enumerator = nullptr; + if(FAILED(hr)) { - ComPtr<IMMDeviceEnumerator> enumerator{static_cast<IMMDeviceEnumerator*>(ptr)}; - if(mDevId.empty()) - hr = enumerator->GetDefaultAudioEndpoint(eRender, eMultimedia, mMMDev.getPtr()); - else - hr = enumerator->GetDevice(mDevId.c_str(), mMMDev.getPtr()); + WARN("Failed to open device \"%s\"\n", name?name:"(default)"); + return hr; } - if(SUCCEEDED(hr)) - hr = mMMDev->Activate(IID_IAudioClient, CLSCTX_INPROC_SERVER, nullptr, &ptr); - if(SUCCEEDED(hr)) + + hr = mmdev->Activate(IID_IAudioClient, CLSCTX_INPROC_SERVER, nullptr, &ptr); + if(FAILED(hr)) { - mClient = ComPtr<IAudioClient>{static_cast<IAudioClient*>(ptr)}; - if(mDevice->DeviceName.empty()) - mDevice->DeviceName = get_device_name_and_guid(mMMDev.get()).first; + WARN("Failed to activate device \"%s\"\n", name?name:"(default)"); + return hr; } - if(FAILED(hr)) - mMMDev = nullptr; + mClient = ComPtr<IAudioClient>{static_cast<IAudioClient*>(ptr)}; + mMMDev = std::move(mmdev); + if(name) mDevice->DeviceName = name; + else mDevice->DeviceName = get_device_name_and_guid(mMMDev.get()).first; return hr; } @@ -1105,8 +1113,7 @@ HRESULT WasapiPlayback::resetProxy() } mFrameStep = OutputType.Format.nChannels; - EndpointFormFactor formfactor{UnknownFormFactor}; - get_device_formfactor(mMMDev.get(), &formfactor); + const EndpointFormFactor formfactor{get_device_formfactor(mMMDev.get())}; mDevice->IsHeadphones = (mDevice->FmtChans == DevFmtStereo && (formfactor == Headphones || formfactor == Headset)); @@ -1226,7 +1233,7 @@ struct WasapiCapture final : public BackendBase, WasapiProxy { int recordProc(); void open(const char *name) override; - HRESULT openProxy() override; + HRESULT openProxy(const char *name) override; void closeProxy() override; HRESULT resetProxy() override; @@ -1238,8 +1245,6 @@ struct WasapiCapture final : public BackendBase, WasapiProxy { void captureSamples(al::byte *buffer, uint samples) override; uint availableSamples() override; - std::wstring mDevId; - HRESULT mOpenStatus{E_FAIL}; ComPtr<IMMDevice> mMMDev{nullptr}; ComPtr<IAudioClient> mClient{nullptr}; @@ -1373,48 +1378,15 @@ void WasapiCapture::open(const char *name) if(SUCCEEDED(hr)) { - if(name) - { - if(CaptureDevices.empty()) - pushMessage(MsgType::EnumerateCapture).wait(); - - hr = E_FAIL; - auto iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(), - [name](const DevMap &entry) -> bool - { return entry.name == name || entry.endpoint_guid == name; }); - if(iter == CaptureDevices.cend()) - { - const std::wstring wname{utf8_to_wstr(name)}; - iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(), - [&wname](const DevMap &entry) -> bool - { return entry.devid == wname; }); - } - if(iter == CaptureDevices.cend()) - WARN("Failed to find device name matching \"%s\"\n", name); - else - { - mDevId = iter->devid; - mDevice->DeviceName = iter->name; - hr = S_OK; - } - } + if(name && CaptureDevices.empty()) + pushMessage(MsgType::EnumerateCapture).wait(); + hr = pushMessage(MsgType::OpenDevice, name).get(); } - - if(SUCCEEDED(hr)) - hr = pushMessage(MsgType::OpenDevice).get(); mOpenStatus = hr; if(FAILED(hr)) - { - if(mNotifyEvent != nullptr) - CloseHandle(mNotifyEvent); - mNotifyEvent = nullptr; - - mDevId.clear(); - throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx", hr}; - } hr = pushMessage(MsgType::ResetDevice).get(); if(FAILED(hr)) @@ -1425,30 +1397,56 @@ void WasapiCapture::open(const char *name) } } -HRESULT WasapiCapture::openProxy() +HRESULT WasapiCapture::openProxy(const char *name) { + const wchar_t *devid{nullptr}; + if(name) + { + auto iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(), + [name](const DevMap &entry) -> bool + { return entry.name == name || entry.endpoint_guid == name; }); + if(iter == CaptureDevices.cend()) + { + const std::wstring wname{utf8_to_wstr(name)}; + iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(), + [&wname](const DevMap &entry) -> bool + { return entry.devid == wname; }); + } + if(iter == CaptureDevices.cend()) + { + WARN("Failed to find device name matching \"%s\"\n", name); + return E_FAIL; + } + name = iter->name.c_str(); + devid = iter->devid.c_str(); + } + void *ptr; HRESULT hr{CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER, IID_IMMDeviceEnumerator, &ptr)}; - if(SUCCEEDED(hr)) - { - ComPtr<IMMDeviceEnumerator> enumerator{static_cast<IMMDeviceEnumerator*>(ptr)}; - if(mDevId.empty()) - hr = enumerator->GetDefaultAudioEndpoint(eCapture, eMultimedia, mMMDev.getPtr()); - else - hr = enumerator->GetDevice(mDevId.c_str(), mMMDev.getPtr()); - } - if(SUCCEEDED(hr)) - hr = mMMDev->Activate(IID_IAudioClient, CLSCTX_INPROC_SERVER, nullptr, &ptr); - if(SUCCEEDED(hr)) + ComPtr<IMMDeviceEnumerator> enumerator{static_cast<IMMDeviceEnumerator*>(ptr)}; + if(!devid) + hr = enumerator->GetDefaultAudioEndpoint(eCapture, eMultimedia, mMMDev.getPtr()); + else + hr = enumerator->GetDevice(devid, mMMDev.getPtr()); + enumerator = nullptr; + if(FAILED(hr)) { - mClient = ComPtr<IAudioClient>{static_cast<IAudioClient*>(ptr)}; - if(mDevice->DeviceName.empty()) - mDevice->DeviceName = get_device_name_and_guid(mMMDev.get()).first; + WARN("Failed to open device \"%s\"\n", name?name:"(default)"); + return hr; } + hr = mMMDev->Activate(IID_IAudioClient, CLSCTX_INPROC_SERVER, nullptr, &ptr); if(FAILED(hr)) + { + WARN("Failed to activate device \"%s\"\n", name?name:"(default)"); mMMDev = nullptr; + return hr; + } + + mClient = ComPtr<IAudioClient>{static_cast<IAudioClient*>(ptr)}; + if(name) mDevice->DeviceName = name; + else mDevice->DeviceName = get_device_name_and_guid(mMMDev.get()).first; return hr; } diff --git a/alc/backends/wave.cpp b/alc/backends/wave.cpp index 4f738230..e80fb3ae 100644 --- a/alc/backends/wave.cpp +++ b/alc/backends/wave.cpp @@ -132,8 +132,8 @@ int WaveBackend::mixerProc() int64_t done{0}; auto start = std::chrono::steady_clock::now(); - while(!mKillNow.load(std::memory_order_acquire) && - mDevice->Connected.load(std::memory_order_acquire)) + while(!mKillNow.load(std::memory_order_acquire) + && mDevice->Connected.load(std::memory_order_acquire)) { auto now = std::chrono::steady_clock::now(); @@ -214,9 +214,12 @@ void WaveBackend::open(const char *name) throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", name}; + /* There's only one "device", so if it's already open, we're done. */ + if(mFile) return; + #ifdef _WIN32 { - std::wstring wname = utf8_to_wstr(fname); + std::wstring wname{utf8_to_wstr(fname)}; mFile = _wfopen(wname.c_str(), L"wb"); } #else diff --git a/alc/backends/winmm.cpp b/alc/backends/winmm.cpp index 9b88f12e..14fda45b 100644 --- a/alc/backends/winmm.cpp +++ b/alc/backends/winmm.cpp @@ -224,27 +224,28 @@ void WinMMPlayback::open(const char *name) auto DeviceID = static_cast<UINT>(std::distance(PlaybackDevices.cbegin(), iter)); retry_open: - mFormat = WAVEFORMATEX{}; + WAVEFORMATEX format{}; if(mDevice->FmtType == DevFmtFloat) { - mFormat.wFormatTag = WAVE_FORMAT_IEEE_FLOAT; - mFormat.wBitsPerSample = 32; + format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT; + format.wBitsPerSample = 32; } else { - mFormat.wFormatTag = WAVE_FORMAT_PCM; + format.wFormatTag = WAVE_FORMAT_PCM; if(mDevice->FmtType == DevFmtUByte || mDevice->FmtType == DevFmtByte) - mFormat.wBitsPerSample = 8; + format.wBitsPerSample = 8; else - mFormat.wBitsPerSample = 16; + format.wBitsPerSample = 16; } - mFormat.nChannels = ((mDevice->FmtChans == DevFmtMono) ? 1 : 2); - mFormat.nBlockAlign = static_cast<WORD>(mFormat.wBitsPerSample * mFormat.nChannels / 8); - mFormat.nSamplesPerSec = mDevice->Frequency; - mFormat.nAvgBytesPerSec = mFormat.nSamplesPerSec * mFormat.nBlockAlign; - mFormat.cbSize = 0; - - MMRESULT res{waveOutOpen(&mOutHdl, DeviceID, &mFormat, + format.nChannels = ((mDevice->FmtChans == DevFmtMono) ? 1 : 2); + format.nBlockAlign = static_cast<WORD>(format.wBitsPerSample * format.nChannels / 8); + format.nSamplesPerSec = mDevice->Frequency; + format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign; + format.cbSize = 0; + + HWAVEOUT outHandle{}; + MMRESULT res{waveOutOpen(&outHandle, DeviceID, &format, reinterpret_cast<DWORD_PTR>(&WinMMPlayback::waveOutProcC), reinterpret_cast<DWORD_PTR>(this), CALLBACK_FUNCTION)}; if(res != MMSYSERR_NOERROR) @@ -257,6 +258,11 @@ retry_open: throw al::backend_exception{al::backend_error::DeviceError, "waveOutOpen failed: %u", res}; } + if(mOutHdl) + waveOutClose(mOutHdl); + mOutHdl = outHandle; + mFormat = format; + mDevice->DeviceName = PlaybackDevices[DeviceID]; } |