Skip to content

Commit

Permalink
Merge branch 'master' into fix-sapi5-continuous-reading
Browse files Browse the repository at this point in the history
  • Loading branch information
gexgd0419 authored Jan 9, 2025
2 parents 0dec8e7 + e06f2b8 commit 862a282
Show file tree
Hide file tree
Showing 47 changed files with 3,282 additions and 481 deletions.
90 changes: 45 additions & 45 deletions nvdaHelper/local/wasapi.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
This file is a part of the NVDA project.
URL: http://www.nvda-project.org/
Copyright 2023 James Teh.
Copyright 2023-2024 NV Access Limited, James Teh.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2.0, as published by
the Free Software Foundation.
Expand Down Expand Up @@ -45,6 +45,7 @@ const IID IID_IMMNotificationClient = __uuidof(IMMNotificationClient);
const IID IID_IAudioStreamVolume = __uuidof(IAudioStreamVolume);
const IID IID_IAudioSessionManager2 = __uuidof(IAudioSessionManager2);
const IID IID_IAudioSessionControl2 = __uuidof(IAudioSessionControl2);
const IID IID_IMMEndpoint = __uuidof(IMMEndpoint);

/**
* C++ RAII class to manage the lifecycle of a standard Windows HANDLE closed
Expand Down Expand Up @@ -167,9 +168,9 @@ class WasapiPlayer {

/**
* Constructor.
* Specify an empty (not null) deviceName to use the default device.
* Specify an empty (not null) endpointId to use the default device.
*/
WasapiPlayer(wchar_t* deviceName, WAVEFORMATEX format,
WasapiPlayer(wchar_t* endpointId, WAVEFORMATEX format,
ChunkCompletedCallback callback);

/**
Expand Down Expand Up @@ -229,7 +230,7 @@ class WasapiPlayer {
CComPtr<IAudioClock> clock;
// The maximum number of frames that will fit in the buffer.
UINT32 bufferFrames;
std::wstring deviceName;
std::wstring endpointId;
WAVEFORMATEX format;
ChunkCompletedCallback callback;
PlayState playState = PlayState::stopped;
Expand All @@ -246,9 +247,9 @@ class WasapiPlayer {
bool isUsingPreferredDevice = false;
};

WasapiPlayer::WasapiPlayer(wchar_t* deviceName, WAVEFORMATEX format,
WasapiPlayer::WasapiPlayer(wchar_t* endpointId, WAVEFORMATEX format,
ChunkCompletedCallback callback)
: deviceName(deviceName), format(format), callback(callback) {
: endpointId(endpointId), format(format), callback(callback) {
wakeEvent = CreateEvent(nullptr, false, false, nullptr);
}

Expand All @@ -266,7 +267,7 @@ HRESULT WasapiPlayer::open(bool force) {
}
CComPtr<IMMDevice> device;
isUsingPreferredDevice = false;
if (deviceName.empty()) {
if (endpointId.empty()) {
hr = enumerator->GetDefaultAudioEndpoint(eRender, eConsole, &device);
} else {
hr = getPreferredDevice(device);
Expand Down Expand Up @@ -491,48 +492,47 @@ HRESULT WasapiPlayer::getPreferredDevice(CComPtr<IMMDevice>& preferredDevice) {
if (FAILED(hr)) {
return hr;
}
CComPtr<IMMDeviceCollection> devices;
hr = enumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &devices);
CComPtr<IMMDevice> device;
hr = enumerator->GetDevice(endpointId.c_str(), &device);
if (FAILED(hr)) {
return hr;
}
UINT count = 0;
devices->GetCount(&count);
for (UINT d = 0; d < count; ++d) {
CComPtr<IMMDevice> device;
hr = devices->Item(d, &device);
if (FAILED(hr)) {
return hr;
}
CComPtr<IPropertyStore> props;
hr = device->OpenPropertyStore(STGM_READ, &props);
if (FAILED(hr)) {
return hr;
}
PROPVARIANT val;
hr = props->GetValue(PKEY_Device_FriendlyName, &val);
if (FAILED(hr)) {
return hr;
}
// WinMM device names are truncated to MAXPNAMELEN characters, including the
// null terminator.
constexpr size_t MAX_CHARS = MAXPNAMELEN - 1;
if (wcsncmp(val.pwszVal, deviceName.c_str(), MAX_CHARS) == 0) {
PropVariantClear(&val);
preferredDevice = std::move(device);
return S_OK;
}
PropVariantClear(&val);

// We only want to use the device if it is plugged in and enabled.
DWORD state;
hr = device->GetState(&state);
if (FAILED(hr)) {
return hr;
} else if (state != DEVICE_STATE_ACTIVE) {
return E_NOTFOUND;
}
return E_NOTFOUND;

// We only want to use the device if it is an output device.
IMMEndpoint* endpoint;
hr = device->QueryInterface(IID_IMMEndpoint, (void**)&endpoint);
if (FAILED(hr)) {
return hr;
}
EDataFlow dataFlow;
hr = endpoint->GetDataFlow(&dataFlow);
if (FAILED(hr)) {
return hr;
} else if (dataFlow != eRender) {
return E_NOTFOUND;
}
preferredDevice = std::move(device);
endpoint->Release();
device.Release();
enumerator.Release();
return S_OK;
}

bool WasapiPlayer::didPreferredDeviceBecomeAvailable() {
if (
// We're already using the preferred device.
isUsingPreferredDevice ||
// A preferred device was not specified.
deviceName.empty() ||
endpointId.empty() ||
// A device hasn't recently changed state.
deviceStateChangeCount == notificationClient->getDeviceStateChangeCount()
) {
Expand Down Expand Up @@ -673,7 +673,7 @@ HRESULT WasapiPlayer::disableCommunicationDucking(IMMDevice* device) {
*/
class SilencePlayer {
public:
SilencePlayer(wchar_t* deviceName);
SilencePlayer(wchar_t* endpointId);
HRESULT init();
// Play silence for the specified duration.
void playFor(DWORD ms, float volume);
Expand All @@ -698,8 +698,8 @@ class SilencePlayer {
std::vector<INT16> whiteNoiseData;
};

SilencePlayer::SilencePlayer(wchar_t* deviceName):
player(deviceName, getFormat(), nullptr),
SilencePlayer::SilencePlayer(wchar_t* endpointId):
player(endpointId, getFormat(), nullptr),
whiteNoiseData(
SILENCE_BYTES / (
sizeof(INT16) / sizeof(unsigned char)
Expand Down Expand Up @@ -791,10 +791,10 @@ void SilencePlayer::terminate() {
* WasapiPlayer or SilencePlayer, with the exception of wasPlay_startup.
*/

WasapiPlayer* wasPlay_create(wchar_t* deviceName, WAVEFORMATEX format,
WasapiPlayer* wasPlay_create(wchar_t* endpointId, WAVEFORMATEX format,
WasapiPlayer::ChunkCompletedCallback callback
) {
return new WasapiPlayer(deviceName, format, callback);
return new WasapiPlayer(endpointId, format, callback);
}

void wasPlay_destroy(WasapiPlayer* player) {
Expand Down Expand Up @@ -855,9 +855,9 @@ HRESULT wasPlay_startup() {

SilencePlayer* silence = nullptr;

HRESULT wasSilence_init(wchar_t* deviceName) {
HRESULT wasSilence_init(wchar_t* endpointId) {
assert(!silence);
silence = new SilencePlayer(deviceName);
silence = new SilencePlayer(endpointId);
return silence->init();
}

Expand Down
Loading

0 comments on commit 862a282

Please sign in to comment.