Skip to content
This repository

[AE] Allow sink to decide how or if it can be suspended #1548

Merged
merged 5 commits into from over 1 year ago

6 participants

Damian Huckle Memphiz Cory Fields davilla arnova Geoffrey McRae
Damian Huckle
Collaborator

Abstract softSuspend/Resume to sink to let it pre-process or decline a Suspend state.

This replaces previous behaviour of destroying the sink on suspend and allows the sink to shitdown gracefully or decline, as well as force a re-init on Resume if required.

Moved suspend/resume code to inline function to clean up SoftAE::Run() and added better comments

Written in light of reported issue with ALSA and Droid sinks on suspend. WASAPI has been implemented here - other platform help requested!

Memphiz
Owner

Shitdown gracefully - lol ymmd

Damian Huckle
Collaborator

"Shitdown gracefully - lol ymmd"

Lmfao - intentional??

Cory Fields
Owner

If the sink knows that it is (or should be) suspended, it will know much better what to do when idle.

I'd really like to see this logic dropped out of the engine and handled in the sinks instead. It's far too complicated trying to keep up with all of the possible states here.

Imo it should go like this:

  • Drop the continuous streaming handling in the engine, sink decides. Some platforms can handle this and some can't, don't try to override them.
  • Always fire data at the sink, even if it's silence.
  • Engine has a number of possible states: starting/stopping/playing/idle/etc. Advertise which.
  • Engine fires a public event when going from idle->playing or the reverse.
  • Sink does a engine->event.Wait() if we're suspended.

If done that way, there's no need for any blind waits, there's no wake/sleep latency, and we're sleeping for the exact right time every time.

Am I missing an obvious reason for keeping this stuff in the engine?

Damian Huckle
Collaborator

@TheUni - thx for the review. I agree with where you're going on the above, especially about it getting complicated in the engine code thus the inlining for that logic.

Sleeping for the exact wait time is ideal (as long as Resume is immediate).

If I have a concern it would be the narrow window we're in. Not sure I can get the above done (nor allow time for other platforms to comply), and I'm doubtful the above would meet the "bug-fix" criteria in feature-freeze.

If it would be acceptable post-merge-window we can skip this and I can put something together more in line with the above. If not this would be the stop-gap to correct the issues you're seeing with ALSA where the engine does not allow the sink to pre-process that state.

davilla
Collaborator

if this is going in, it needs to go in as is, there's no time left for the prefect solution, I'd have ALSA skip suspend/resume, it does not need it as there is no exclusive mode.

Damian Huckle
Collaborator

In which case the returns in the virtual functions are correct - should not need any ALSA sink modification.

I have not tested on an ALSA platform - let alone one showing issues. Gnif reports none on his setup, but clearly some platforms do not allow sink teardown.

Cory Fields
Owner

Closed on purpose? I agree with the above. It's not ideal, but at least it fixes the main issues. I can take a crack at Linux/Android tonight or tomorrow.

davilla
Collaborator

Ping @DDDamian

Deleted user ghost reopened this
Cory Fields
Owner

Looks fine for alsa

Cory Fields
Owner

@DDDamian I've pushed the necessary changes for Alsa to my repo: https://github.com/theuni/xbmc/commits/SoftSuspendSink . You'll want to cherry-pick the top 3 to avoid picking up the merge of your branch. Alsa now works perfectly (on my hardware at least).

Imo we should be setting the default virtual functions to false though, in order to assume that the sinks can't sleep. That would give back the old behavior.

Cory Fields
Owner

Ugh, hold on that. Alsa pause seems to mean: lock the whole damn device and render it useless otherwise.

Will investigate.

xbmc/cores/AudioEngine/Interfaces/AESink.h
... ... @@ -87,5 +87,17 @@ class IAESink
87 87 This method sets the volume control, volume ranges from 0.0 to 1.0.
88 88 */
89 89 virtual void SetVolume(float volume) {};
  90 +
  91 + /*
  92 + Requests sink to prepare itself for a suspend state
  93 + @return false if sink cannot be suspended
  94 + */
  95 + virtual bool SoftSuspend() {return true;};
  96 +
8
arnova Collaborator
arnova added a note

I think it would be safer if this was to default to false?!

Damian Huckle Collaborator

Agree - if sink code not correct (can't be paused/flushed/released) or functionality not provided false is safer. Will update. Same might be true for SoftResume() below.

Still no solutions offered for ALSA-based systems. Gnif can't repro and I'm guessing on ALSA fixes...if no solution found before betas I'll setup a Linux box or solicit a forum tester on that platform. Something's borked in that sink or kernal if we can't even pause output.

arnova Collaborator
arnova added a note

I'm running Linux with ALSA so I could perform some tests... But what's not entirely clear is: what's the issue then with ALSA? And does it only occur with this PR or also with mainline? Only thing I've seen with mainline is that XBMC sometimes hangs when restarting within the same X session of which I suspect it's caused by AE.

davilla Collaborator
davilla added a note

ALSA issues are hidden if you have CPU ponies, these issues are showing up on CPU limited or embedded (arm) platforms.

arnova Collaborator
arnova added a note

I'm running it on an Atom N330, so no real pony, right? But you mean high CPU load or race-issues?

davilla Collaborator
davilla added a note

make that N330 pretend it's a single core with no HT and you should see any issues :)

Damian Huckle Collaborator

@davilla, @theuni - you are both seeing the issue - can you describe it for me? Is it just on resume? What actually happens? Is it only with navsounds or streams?

arnova Collaborator
arnova added a note

Since I understand the issues are there regardless (both with and without this PR) shouldn't we just merge this PR so it at least solves the other issues?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
arnova arnova commented on the diff
xbmc/cores/AudioEngine/Engines/SoftAE/SoftAE.cpp
((28 lines not shown))
  1403 + {
  1404 + sinkIsSuspended = false; //sink cannot be suspended
  1405 + m_softSuspend = false; //break suspend loop
  1406 + break;
  1407 + }
  1408 + else
  1409 + sinkIsSuspended = true; //sink has suspended processing
  1410 + sinkLock.Leave();
  1411 + }
  1412 +
  1413 + /* idle for platform-defined time */
  1414 + m_wake.WaitMSec(SOFTAE_IDLE_WAIT_MSEC);
  1415 +
  1416 + /* check if we need to resume for stream or sound */
  1417 + if (!m_isSuspended && (!m_playingStreams.empty() || !m_playing_sounds.empty()))
  1418 + {
2
arnova Collaborator
arnova added a note

Perhaps I'm missing something, but I don't get this logic.

Damian Huckle Collaborator

There's two ways to suspend audio processing:

1) forced Suspend - used by externalplayer.cpp or addons to force release of audio context so external players or system can use audio device, e.g. when launching a YouTube vid in browser or a BD-menu-capable external player. Signified by m_isSuspended == true.

2) "soft" Suspend - slips into idle mode 10 seconds after last stream or sound played - reduces battery consumption / CPU load and releases audio context as above. Signified by m_softSuspend == true && 10 sec delay elapsed.

This PR just moves the decision on what suspend states (if any) the platform is capable of (and really, we're just pausing output and waiting for something to play!) to the sink itself.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Damian Huckle DDDamian referenced this pull request
Closed

AE + Alsa fixes #1820

Damian Huckle DDDamian commented on the diff
xbmc/cores/AudioEngine/Interfaces/AESink.h
@@ -87,5 +87,17 @@ class IAESink
87 87 This method sets the volume control, volume ranges from 0.0 to 1.0.
88 88 */
89 89 virtual void SetVolume(float volume) {};
  90 +
  91 + /*
  92 + Requests sink to prepare itself for a suspend state
  93 + @return false if sink cannot be suspended
  94 + */
  95 + virtual bool SoftSuspend() {return false;};
  96 +
1
Damian Huckle Collaborator

Changed now to default to false so sink cannot be suspended as requested. But this does mean that without softSuspend()/softResume() functions in the ALSA sink it will not benefit from softSuspend (idling when nothing playing). It shouldn't need to be anything fancy as ALSA doesn't take exclusive control like WASAPI or CA, but wanted to point that out.

That's why I originally defaulted to true to take advantage of the wait state....

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
davilla
Collaborator

this ready to inject ?

Damian Huckle
Collaborator

From win32 perspective yes, but it was never aimed at that. Absolutely requires sign-off from ALSA tester - especially see line note in AESink.h

Cory Fields
Owner

This is causing tons of buffer underruns with ALSA, I'm poking at it now. Can't rule out local error yet, so I'll report back with what I find. But for now, it's a NAK :(

Cory Fields
Owner

Ok, disregard previous nack. Results are noisy and random, so it's very likely there's no behavioral difference between the two.

Cory Fields
Owner

I think we need to just shove it in and get it tested on more hardware. ALSA is fickle, we'll never get this right without a bigger sample size.

Damian Huckle
Collaborator

@theuni - are you stable with just your fixes in #1820? There's no way I'd recommend pushing this unless that's the case. This is solely for ALSA, except it reduces latency on a re-awake with navsounds after sleep on all platforms.

NAK without ALSA signoff. Gnif says he can't repro on Linux. From my win32 perspective I dont need this minus the slight latency issue mentioned above when a navsound awakes engine.

Really all we're trying to do here is pause ALSA output and restart later. But if we can't pause ALSA and restart I'm not sure where to go with this. Maybe when that Pivos box arrives.

arnova
Collaborator

I'm on ALSA so I could test this but I need to know what to look for...

davilla
Collaborator

Broken pipes, audio thrashing, high cpu, etc. General SoftAE misbehaving. With/without GUI sounds enabled too. Video/Audio startup and seeks.

Damian Huckle
Collaborator

@davilla - I see none of the above on win32. As this stands now with the change in the default values of the virtual functions the sink must override the SoftSuspend() function and return true to gain the benefits of the idle time. If this isn't done your CPU usage will rise as only AddPackets() can block. See the line note in AESink.h.

arnova
Collaborator

@Davilla: All those issues only on low-powered (ARM) systems like RPI/aTV?

Cory Fields
Owner

Alsa will block in AddPackets. The snd_pcm_wait() blocks (using select() internally) while waiting for free space in the alsa buffer. So it's not sucking up too much cpu time.

@DDDamian From my experience, Alsa simply isn't reliable enough to bring up/down all the time. I would be thrilled if someone proved me wrong here, but I spent tons of time trying to get it to play nice on my various hardware, with nothing to show for it. Until then, we'll need to leave the SoftSuspend disabled there.

Cory Fields
Owner

So this has my sign-off, we need it for b2. what we have now is much worse than b1 due to the constant create/destroy of the sink.

davilla davilla merged commit f528cd3 into from
Damian Huckle
Collaborator

@theuni - thx for all the efforts here. We're both flying a little blind. Hoping this provides some resolution or at least a bridge until some further ALSA work addresses those issues.

arnova
Collaborator

Nice to have this finally (before b2) and since we don't really need Suspend for ALSA I think not using it there totally makes sense :-)

Damian Huckle
Collaborator

Thx Spiff - nice to see you're still around :)

Memphiz
Owner

He seems to hide - did you mixup nicks? Or is there something i need to know? ;)

arnova
Collaborator

spiff != arnova last time I checked ;-)

Damian Huckle
Collaborator

doh! sorry more caffeine (and less git-spam) lol

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
78 xbmc/cores/AudioEngine/Engines/SoftAE/SoftAE.cpp
@@ -1017,34 +1017,8 @@ void CSoftAE::Run()
1017 1017 restart = true;
1018 1018 }
1019 1019
1020   - if (m_playingStreams.empty() && m_playing_sounds.empty() && m_streams.empty() &&
1021   - !m_softSuspend && !g_advancedSettings.m_streamSilence)
1022   - {
1023   - m_softSuspend = true;
1024   - m_softSuspendTimer = XbmcThreads::SystemClockMillis() + 10000; //10.0 second delay for softSuspend
1025   - }
1026   -
1027   - unsigned int curSystemClock = XbmcThreads::SystemClockMillis();
1028   -
1029   - /* idle while in Suspend() state until Resume() called */
1030   - /* idle if nothing to play and user hasn't enabled */
1031   - /* continuous streaming (silent stream) in as.xml */
1032   - while ((m_isSuspended || (m_softSuspend && (curSystemClock > m_softSuspendTimer))) &&
1033   - m_running && !m_reOpen && !restart)
1034   - {
1035   - if (m_sink)
1036   - {
1037   - /* take the sink lock */
1038   - CExclusiveLock sinkLock(m_sinkLock);
1039   - //m_sink->Drain(); TODO: implement
1040   - m_sink->Deinitialize();
1041   - delete m_sink;
1042   - m_sink = NULL;
1043   - }
1044   - if (!m_playingStreams.empty() || !m_playing_sounds.empty() || !m_sounds.empty())
1045   - m_softSuspend = false;
1046   - m_wake.WaitMSec(SOFTAE_IDLE_WAIT_MSEC);
1047   - }
  1020 + /* Handle idle or forced suspend */
  1021 + ProcessSuspend();
1048 1022
1049 1023 /* if we are told to restart */
1050 1024 if (m_reOpen || restart || !m_sink)
@@ -1405,3 +1379,51 @@ inline void CSoftAE::RemoveStream(StreamList &streams, CSoftAEStream *stream)
1405 1379 m_streamsPlaying = !m_playingStreams.empty();
1406 1380 }
1407 1381
  1382 +inline void CSoftAE::ProcessSuspend()
  1383 +{
  1384 + bool sinkIsSuspended = false;
  1385 +
  1386 + if (m_playingStreams.empty() && m_playing_sounds.empty() &&
  1387 + !m_softSuspend && !g_advancedSettings.m_streamSilence)
  1388 + {
  1389 + m_softSuspend = true;
  1390 + m_softSuspendTimer = XbmcThreads::SystemClockMillis() + 10000; //10.0 second delay for softSuspend
  1391 + }
  1392 +
  1393 + unsigned int curSystemClock = XbmcThreads::SystemClockMillis();
  1394 +
  1395 + /* idle while in Suspend() state until Resume() called */
  1396 + /* idle if nothing to play and user hasn't enabled */
  1397 + /* continuous streaming (silent stream) in as.xml */
  1398 + while ((m_isSuspended || (m_softSuspend && (curSystemClock > m_softSuspendTimer))) &&
  1399 + m_running && !m_reOpen)
  1400 + {
  1401 + if (m_sink && !sinkIsSuspended)
  1402 + {
  1403 + /* put the sink in Suspend mode */
  1404 + CExclusiveLock sinkLock(m_sinkLock);
  1405 + if (!m_sink->SoftSuspend())
  1406 + {
  1407 + sinkIsSuspended = false; //sink cannot be suspended
  1408 + m_softSuspend = false; //break suspend loop
  1409 + break;
  1410 + }
  1411 + else
  1412 + sinkIsSuspended = true; //sink has suspended processing
  1413 + sinkLock.Leave();
  1414 + }
  1415 +
  1416 + /* idle for platform-defined time */
  1417 + m_wake.WaitMSec(SOFTAE_IDLE_WAIT_MSEC);
  1418 +
  1419 + /* check if we need to resume for stream or sound */
  1420 + if (!m_isSuspended && (!m_playingStreams.empty() || !m_playing_sounds.empty()))
  1421 + {
  1422 + m_reOpen = !m_sink->SoftResume(); // sink returns false if it requires reinit
  1423 + sinkIsSuspended = false; //sink processing data
  1424 + m_softSuspend = false; //break suspend loop
  1425 + break;
  1426 + }
  1427 + }
  1428 +}
  1429 +
9 xbmc/cores/AudioEngine/Engines/SoftAE/SoftAE.h
@@ -120,6 +120,8 @@ class CSoftAE : public IThreadedAE
120 120 bool SetupEncoder(AEAudioFormat &format);
121 121 void Deinitialize();
122 122
  123 + inline void ProcessSuspend(); /* enter suspend state if nothing to play and sink allows */
  124 +
123 125 inline void GetDeviceFriendlyName(std::string &device);
124 126
125 127 IAESink *GetSink(AEAudioFormat &desiredFormat, bool passthrough, std::string &device);
@@ -133,9 +135,10 @@ class CSoftAE : public IThreadedAE
133 135 bool m_stereoUpmix;
134 136
135 137 /* internal vars */
136   - bool m_running, m_reOpen, m_isSuspended;
137   - bool m_softSuspend; /* latches after last stream or sound played for timer below */
138   - unsigned int m_softSuspendTimer; /* time in milliseconds to hold sink open before soft suspend */
  138 + bool m_running, m_reOpen;
  139 + bool m_isSuspended; /* engine suspended by external function to release audio context */
  140 + bool m_softSuspend; /* latches after last stream or sound played for timer below for idle */
  141 + unsigned int m_softSuspendTimer; /* time in milliseconds to hold sink open before soft suspend for idle */
139 142 CEvent m_reOpenEvent;
140 143 CEvent m_wake;
141 144
12 xbmc/cores/AudioEngine/Interfaces/AESink.h
@@ -87,5 +87,17 @@ class IAESink
87 87 This method sets the volume control, volume ranges from 0.0 to 1.0.
88 88 */
89 89 virtual void SetVolume(float volume) {};
  90 +
  91 + /*
  92 + Requests sink to prepare itself for a suspend state
  93 + @return false if sink cannot be suspended
  94 + */
  95 + virtual bool SoftSuspend() {return false;};
  96 +
  97 + /*
  98 + Notify sink to prepare to resume processing after suspend state
  99 + @return false if sink must be reinitialized
  100 + */
  101 + virtual bool SoftResume() {return false;};
90 102 };
91 103
6 xbmc/cores/AudioEngine/Sinks/AESinkALSA.cpp
@@ -489,9 +489,6 @@ unsigned int CAESinkALSA::AddPackets(uint8_t *data, unsigned int frames, bool ha
489 489 if (!m_pcm)
490 490 return 0;
491 491
492   - if (snd_pcm_state(m_pcm) == SND_PCM_STATE_PREPARED)
493   - snd_pcm_start(m_pcm);
494   -
495 492 int ret;
496 493
497 494 ret = snd_pcm_avail(m_pcm);
@@ -520,6 +517,9 @@ unsigned int CAESinkALSA::AddPackets(uint8_t *data, unsigned int frames, bool ha
520 517 }
521 518 }
522 519
  520 + if ( ret > 0 && snd_pcm_state(m_pcm) == SND_PCM_STATE_PREPARED)
  521 + snd_pcm_start(m_pcm);
  522 +
523 523 return ret;
524 524 }
525 525
24 xbmc/cores/AudioEngine/Sinks/AESinkWASAPI.cpp
@@ -331,7 +331,8 @@ void CAESinkWASAPI::Deinitialize()
331 331 {
332 332 try
333 333 {
334   - m_pAudioClient->Stop();
  334 + m_pAudioClient->Stop(); //stop the audio output
  335 + m_pAudioClient->Reset(); //flush buffer and reset audio clock stream position
335 336 }
336 337 catch (...)
337 338 {
@@ -537,6 +538,27 @@ unsigned int CAESinkWASAPI::AddPackets(uint8_t *data, unsigned int frames, bool
537 538 return NumFramesRequested;
538 539 }
539 540
  541 +bool CAESinkWASAPI::SoftSuspend()
  542 +{
  543 + /* Sink has been asked to suspend output - we release audio */
  544 + /* device as we are in exclusive mode and thus allow external */
  545 + /* audio sources to play. This requires us to reinitialize */
  546 + /* on resume. */
  547 +
  548 + Deinitialize();
  549 +
  550 + return true;
  551 +}
  552 +
  553 +bool CAESinkWASAPI::SoftResume()
  554 +{
  555 + /* Sink asked to resume output. To release audio device in */
  556 + /* exclusive mode we release the device context and therefore */
  557 + /* must reinitialize. Return false to force re-init by engine */
  558 +
  559 + return false;
  560 +}
  561 +
540 562 void CAESinkWASAPI::EnumerateDevicesEx(AEDeviceInfoList &deviceInfoList)
541 563 {
542 564 IMMDeviceEnumerator* pEnumerator = NULL;
3  xbmc/cores/AudioEngine/Sinks/AESinkWASAPI.h
@@ -43,6 +43,8 @@ class CAESinkWASAPI : public IAESink
43 43 virtual double GetCacheTime ();
44 44 virtual double GetCacheTotal ();
45 45 virtual unsigned int AddPackets (uint8_t *data, unsigned int frames, bool hasAudio);
  46 + virtual bool SoftSuspend ();
  47 + virtual bool SoftResume ();
46 48 static void EnumerateDevicesEx (AEDeviceInfoList &deviceInfoList);
47 49 private:
48 50 bool InitializeExclusive(AEAudioFormat &format);
@@ -70,6 +72,7 @@ class CAESinkWASAPI : public IAESink
70 72
71 73 bool m_running;
72 74 bool m_initialized;
  75 + bool m_isSuspended; /* sink is in a suspended state - release audio device */
73 76 bool m_isDirty; /* sink output failed - needs re-init or new device */
74 77
75 78 unsigned int m_uiBufferLen; /* wasapi endpoint buffer size, in frames */

Tip: You can add notes to lines in a file. Hover to the left of a line to make a note

Something went wrong with that request. Please try again.