Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

nvwave: Don't close the audio device unless there hasn't been any audio played for 10 seconds. #11024

Merged
merged 4 commits into from
Aug 10, 2020

Conversation

jcsteh
Copy link
Contributor

@jcsteh jcsteh commented Apr 17, 2020

Link to issue number:

Fixes #5172. Fixes #10721 completely.

Summary of the issue:

  1. As described in make the audio device still active for better response #5172, some audio drivers/hardware take a long time to open the device and/or truncate the start/end of the audio.
  2. As described in OneCore performance #10721 (comment), calling WaveOutOpen in the OneCore synth callback mysteriously blocks (and thus lags) for ~100 ms. Because we close the audio device on idle, we can trigger this problem. Although oneCore synth: Fix lag between utterances introduced in NVDA 2019.3. #11023 mostly fixes this, it's impossible (or at least very difficult) to resolve this completely from within the OneCore driver.
  3. Aside from all of this, closing and opening the audio device for rapid short utterances (e.g. rapid movement with the cursor keys or typing) doesn't seem particularly optimal. It's difficult to measure this, but I'd say mitigating this is likely to make audio performance faster/smoother.

Description of how this pull request fixes the issue:

When WavePlayer.idle() is called and closeWhenIdle is set, instead of closing the device immediately after the audio finishes, set a timer. If audio is played before the timer elapses, stop the timer. Close the device when the timer expires.

Testing performed:

Tested as per the STR in #10721 (comment).
It's impossible to know when the device is closed, but I placed a beep in WavePlayer._close, had NVDA speak an utterance, then waited 10 seconds and observed that the beep played. I observed that the beep didn't play unless there was a 10 second period of silence.
Anecdotally, response feels a bit snappier with eSpeak when moving rapidly, but this could be placebo effect; I haven't measured this.

Known issues with pull request:

None.

Change log entry:

@jcsteh jcsteh mentioned this pull request Apr 17, 2020
@Brian1Gaff
Copy link

Brian1Gaff commented Apr 17, 2020 via email

@feerrenrut feerrenrut added bug component/audio NVDA's audio output (nvWave, issues with usb audio etc). labels Apr 17, 2020
source/nvwave.py Outdated Show resolved Hide resolved
@jcsteh
Copy link
Contributor Author

jcsteh commented Apr 18, 2020 via email

@AppVeyorBot
Copy link

See test results for failed build of commit 16e67864eb

@AppVeyorBot
Copy link

See test results for failed build of commit aa38fb321c

@LeonarddeR
Copy link
Collaborator

We're going to use the timer again with the same callback, so the only thing setting it to None does is mean we have to recreate it when we next need it, which seems wasteful. Reusing a timer is perfectly valid. Unless I'm missing a concern you have?

Thanks for clarifying.

@@ -158,13 +163,21 @@ def __init__(self, channels, samplesPerSec, bitsPerSample,
self._lock = threading.RLock()
self.open()

def _callOnMainThread(self, func, *args):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't this function be a candidate for inclusion in core.py beside callLater` you added there as of a59e007?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't notify the watchdog, etc., so it's not safe for general usage. Normally, you'd use queueHandler.queueFunction to run something in the main thread, but I didn't want to deal with the overhead of the core pump, etc. for this case; it really should run ASAP.

self._close()
if not self._closeTimer:
# We need a cancellable timer, so we can't use core.callLater.
self._closeTimer = wx.PyTimer(self._close)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not use here a regular threading.Timer or even wx.Timer?
Note wx.PyTimer seems to have gone away of wxPython documentation as of the Phoenix migration for some reason (maybe a bad one, no clue)
Sorry if I'm missing the point here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

threading.Timer would spawn another thread and execute the function there. That's pretty wasteful, since we already have an event loop in our main thread. It also increases the potential for race conditions.
wx.Timer doesn't allow you to pass in a callback, so you have to subclass it. That makes things a lot less readable.

@Neurrone
Copy link

Neurrone commented May 8, 2020

Any chance the default of 10 seconds could be user configurable? For example, there are situations that I might want to set a longer timer and make that trade-off with battery power.

@k-kolev1985
Copy link

Will this functionality have an option to enable/disable it? Also, as mentioned above, a configurable timeout would also be welcome. Thanks in advance!

@Adriani90
Copy link
Collaborator

There is no need for optional setting here, this happens in the background and is standard for any screen reader, even voice over plays silence for about 6 seconds but the user does not notice it.
I understand that people fear that this would have significant impact on batery, but this is not the case here. The impact is neglegible.

@Adriani90
Copy link
Collaborator

A timing interval is also not needed, this would only overload the gui with additional settings which are not standard for any screen reader aat all. I understand that every user wants to have full control over every small setting in the screen reader, but this is not good from a design perspective. But in the end it is Jamie's decision and the decision of NV Access to implement it in whichever way.

@XLTechie
Copy link
Collaborator

XLTechie commented May 16, 2020 via email

@k-kolev1985
Copy link

@XLTechie The ability to disable it is for battery saving in cases where this NVDA functionality is not needed (e.g. on portable computers which do not exhibit audio cut out behavior). But if really the impact of this functionality on the battery life is negligible, maybe adding the toggle for it it is not worth it.

As for the timeout period configurability. Some people may interrupt their work for longer periods of time but they prefer to still not experience initial speech cut outs when they resume their work.

@codeofdusk
Copy link
Contributor

Any updates on this PR?

@codeofdusk
Copy link
Contributor

Any updates on this? I suspect it'll help a lot with modern machines that aggressively close the sound card for power savings.

@feerrenrut feerrenrut dismissed stale reviews from michaelDCurran, LeonarddeR, and themself via 789d725 August 10, 2020 13:12
@feerrenrut feerrenrut merged commit 633435d into nvaccess:master Aug 10, 2020
@nvaccessAuto nvaccessAuto added this to the 2020.3 milestone Aug 10, 2020
@jcsteh jcsteh deleted the nvwaveIdle branch August 10, 2020 21:02
@Adriani90
Copy link
Collaborator

I get this error now in last alpha from time to time:

ERROR - unhandled exception (23:47:05.138) - MainThread (9068):
Traceback (most recent call last):
  File "wx\core.pyc", line 2196, in Notify
  File "nvwave.pyc", line 349, in _close
  File "nvwave.pyc", line 90, in _winmm_errcheck
OSError: [Errno 5] Das angegebene Gerätehandle ist ungültig.

It occurs quite often. Especially in Windows Explorer on Windows 10 2004.

I am using a Dell XPS13 with a Realtec sound card.

Any idea on how to fix this?

@jcsteh
Copy link
Contributor Author

jcsteh commented Aug 12, 2020 via email

@feerrenrut
Copy link
Contributor

@jcsteh Given #11482 I'm tempted to revert this PR and aim for 2020.4 instead. That will take some pressure off fixing this issue and resolving other queries that are popping up.

@jcsteh
Copy link
Contributor Author

jcsteh commented Aug 13, 2020

Note that #11482 has no user impact apart from the logged error, which won't be noticed in release. I'm not sure what other specific issues you're referring to, but anything related to audio glitches during speech already existed and is unrelated to this PR. That said, this is obviously your call to make.

@feerrenrut
Copy link
Contributor

There is also #11490. There have been questions about whether silence should be sent to devices, questions about the impact on power usage and user configuration. In addition I'm not sure I have seen any confirmation that this fixes issues for anyone. Given the short time to release these problems aren't likely to be resolved.

@Mohamed00
Copy link

As the creator of both issues, I will say that even with the problems it has, I still find that it makes NVDA much more snappy on my machine, and things like rapidly moving through list items seem to be much faster.

@jcsteh
Copy link
Contributor Author

jcsteh commented Aug 14, 2020

For reference, there's also this tweet which suggests it greatly helps another user. Still, I totally understand the desire to mitigate risk; just providing another data point.

@codeofdusk
Copy link
Contributor

I strongly vote to keep this PR despite the issues, as the new issues it introduces are (at least in my opinion) far less severe than those fixed by the patch.

However, I definitely think that it's worth considering in next release further improvements to this functionality, particularly playing silence instead of just keeping the device open, with something like #11481 to control the duration of silence.

@codeofdusk
Copy link
Contributor

I think the changelog entry might need to be revised though, to focus more on speech performance after brief utterances more than truncation of audio (silence would fix the latter issue, holding open the device seems to only fix the former).

@nidza07
Copy link
Contributor

nidza07 commented Aug 14, 2020

Hello. Yes, I am the author of that tweet, and this PR literally fixes all performance issues I had on my machine with NVDA. Before, progress bar beeps would stutter a lot if played at the same time as speech, now this does not happen at all and the beeps are completely smooth, and as another user correctly mentioned the list navigation is a lot snappier than before. After updating to the alpha where this was merged, it felt like a completely new machine. So for me, the impact of this PR is huge, and I feel it is worth considering keeping this merged unless it causes major issues. I'm not sure if just playing silence is the right solution, since I already have a program doing this on my system for bluetooth devices, and it did not provide any performance improvements, unlike this PR. So thanks once again for your work, and hopefully the issues it has will be resolved.

@feerrenrut
Copy link
Contributor

We have done some more testing with this PR, and agree it certainly improves performance significantly in the right circumstances. We will include it in the 2020.3 release, with the intention to fix or mitigate the related issues encountered. Thanks everybody for the extra information provided here.

feerrenrut added a commit that referenced this pull request Aug 20, 2020
This reverts commit 633435d.
Instead of using a timer to close the audio device we will keep it open.
The initial requirement for keeping the device open no longer applies
see #11505 (comment)
@mltony
Copy link
Contributor

mltony commented Aug 27, 2020

I tested this PR in a recent alpha build and I can report that it also fixes the issue with periodic stuttering in Plantronics backbeat headphones. Playing silence didn't fix stuttering.
However, it doesn't seem to fix the issue of the beginning of each utterance being lost - this one can be fixed by playing silence, e.g. via my Bluetooth Audio add-on. I would like to offer my help porting this add-on into NVDA core. Please let me know if NVAccess would be interested in such kind of PR.

@Adriani90
Copy link
Collaborator

Adriani90 commented Aug 27, 2020 via email

feerrenrut added a commit that referenced this pull request Sep 1, 2020
Fixes #5172
Fixes #10721
Fixes #11482
Fixes #11490

### History
#5172
Some audio drivers/hardware take a long time to open the device and/or truncate the start/end of the audio.

#10721
Calling WaveOutOpen in the OneCore synth callback mysteriously blocks (and thus lags) for ~100 ms. Because we close the audio device on idle, we can trigger this problem. Although PR #11023 mostly fixed this, it's impossible (or at least very difficult) to resolve this completely from within the OneCore driver.

PR #11024 attempted to fix these issues by waiting 10 seconds before closing the audio device"

### Problem to solve
Subsequent to #11024, there are occasional exceptions from nvwave, particularly when switching synthesisers. In particular the following cases need to be handled smoothly:
- When using Microsoft Sound Mapper, NVDA should use the Windows default device (even if it changes)
- When the NVDA configured devices becomes invalid, nvWave should fall back to Microsoft Sound Mapper
- When the NVDA configured device becomes available again, NVDA should switch back to using it.
- Handle no audio device at all.

Since these issues needed to be fixed, and also:
- Closing and opening the audio device was originally introduced to support Remote Desktop audio, this is now better served by other solutions. 
- Performance is improved by keeping it open, using a timeout means that the lag is more rare, but will still occur.

### How it is solved
Instead, now don't close the device at all. As per the discussion:
#11505 (comment)

To fix issues with current / preferred device:
- nvWave now saves the preferred device when it is constructed.
- If using a device fails, it is considered unavailable, nvWave attempts to fall back to "Microsoft Sound Mapper".
- From my testing, using "Microsoft Sound Mapper" correctly handled changing the default device (Win 10 2004). It would be helpful if others could confirm, especially on different OS versions.
- During `_idleUnbuffered`
  - The current device is checked to see if it matches the preferred device.
  - The available devices are polled to see if the preferred device is available, if so it switches.
@ondrosik
Copy link
Contributor

I will still use Bluetooth audio addon because I mostly got to a following situation. I sit in a radio studio prepared to read some text. I wait for the song outro (the song is playing from another computer). When It's my time, I press down arrow. NVDA will respond about one second later. I understand that make this configurable may introduce issues mentioned abowe. Maybe we should extend this to one minute but again, some users should come with different situations. I set the bluetooth audio to 10 minutes and simply ignore fact that this Lenovo laptop responds slovly after longer idle time.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug component/audio NVDA's audio output (nvWave, issues with usb audio etc).
Projects
None yet
Development

Successfully merging this pull request may close these issues.

OneCore performance make the audio device still active for better response