Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

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

Merged
merged 5 commits into from

6 participants

@DDDamian
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!

@gnif gnif was assigned
@theuni theuni was assigned
@Memphiz
Owner

Shitdown gracefully - lol ymmd

@DDDamian
Collaborator

"Shitdown gracefully - lol ymmd"

Lmfao - intentional??

@theuni
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?

@DDDamian
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.

@DDDamian
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.

@DDDamian DDDamian closed this
@theuni
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

@ghost ghost reopened this
@theuni
Owner

Looks fine for alsa

@theuni
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.

@theuni
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
This method sets the volume control, volume ranges from 0.0 to 1.0.
*/
virtual void SetVolume(float volume) {};
+
+ /*
+ Requests sink to prepare itself for a suspend state
+ @return false if sink cannot be suspended
+ */
+ virtual bool SoftSuspend() {return true;};
+
@arnova Collaborator
arnova added a note

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

@DDDamian Collaborator
DDDamian added a note

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 :)

@DDDamian Collaborator
DDDamian added a note

@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))
+ {
+ sinkIsSuspended = false; //sink cannot be suspended
+ m_softSuspend = false; //break suspend loop
+ break;
+ }
+ else
+ sinkIsSuspended = true; //sink has suspended processing
+ sinkLock.Leave();
+ }
+
+ /* idle for platform-defined time */
+ m_wake.WaitMSec(SOFTAE_IDLE_WAIT_MSEC);
+
+ /* check if we need to resume for stream or sound */
+ if (!m_isSuspended && (!m_playingStreams.empty() || !m_playing_sounds.empty()))
+ {
@arnova Collaborator
arnova added a note

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

@DDDamian Collaborator
DDDamian added a note

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
@DDDamian DDDamian referenced this pull request
Closed

AE + Alsa fixes #1820

@DDDamian DDDamian commented on the diff
xbmc/cores/AudioEngine/Interfaces/AESink.h
@@ -87,5 +87,17 @@ class IAESink
This method sets the volume control, volume ranges from 0.0 to 1.0.
*/
virtual void SetVolume(float volume) {};
+
+ /*
+ Requests sink to prepare itself for a suspend state
+ @return false if sink cannot be suspended
+ */
+ virtual bool SoftSuspend() {return false;};
+
@DDDamian 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 ?

@DDDamian
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

@theuni
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 :(

@theuni
Owner

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

@theuni
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.

@DDDamian
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.

@DDDamian
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?

@theuni
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.

@theuni
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
@DDDamian
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 :-)

@DDDamian
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 ;-)

@DDDamian
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.
View
78 xbmc/cores/AudioEngine/Engines/SoftAE/SoftAE.cpp
@@ -1017,34 +1017,8 @@ void CSoftAE::Run()
restart = true;
}
- if (m_playingStreams.empty() && m_playing_sounds.empty() && m_streams.empty() &&
- !m_softSuspend && !g_advancedSettings.m_streamSilence)
- {
- m_softSuspend = true;
- m_softSuspendTimer = XbmcThreads::SystemClockMillis() + 10000; //10.0 second delay for softSuspend
- }
-
- unsigned int curSystemClock = XbmcThreads::SystemClockMillis();
-
- /* idle while in Suspend() state until Resume() called */
- /* idle if nothing to play and user hasn't enabled */
- /* continuous streaming (silent stream) in as.xml */
- while ((m_isSuspended || (m_softSuspend && (curSystemClock > m_softSuspendTimer))) &&
- m_running && !m_reOpen && !restart)
- {
- if (m_sink)
- {
- /* take the sink lock */
- CExclusiveLock sinkLock(m_sinkLock);
- //m_sink->Drain(); TODO: implement
- m_sink->Deinitialize();
- delete m_sink;
- m_sink = NULL;
- }
- if (!m_playingStreams.empty() || !m_playing_sounds.empty() || !m_sounds.empty())
- m_softSuspend = false;
- m_wake.WaitMSec(SOFTAE_IDLE_WAIT_MSEC);
- }
+ /* Handle idle or forced suspend */
+ ProcessSuspend();
/* if we are told to restart */
if (m_reOpen || restart || !m_sink)
@@ -1405,3 +1379,51 @@ inline void CSoftAE::RemoveStream(StreamList &streams, CSoftAEStream *stream)
m_streamsPlaying = !m_playingStreams.empty();
}
+inline void CSoftAE::ProcessSuspend()
+{
+ bool sinkIsSuspended = false;
+
+ if (m_playingStreams.empty() && m_playing_sounds.empty() &&
+ !m_softSuspend && !g_advancedSettings.m_streamSilence)
+ {
+ m_softSuspend = true;
+ m_softSuspendTimer = XbmcThreads::SystemClockMillis() + 10000; //10.0 second delay for softSuspend
+ }
+
+ unsigned int curSystemClock = XbmcThreads::SystemClockMillis();
+
+ /* idle while in Suspend() state until Resume() called */
+ /* idle if nothing to play and user hasn't enabled */
+ /* continuous streaming (silent stream) in as.xml */
+ while ((m_isSuspended || (m_softSuspend && (curSystemClock > m_softSuspendTimer))) &&
+ m_running && !m_reOpen)
+ {
+ if (m_sink && !sinkIsSuspended)
+ {
+ /* put the sink in Suspend mode */
+ CExclusiveLock sinkLock(m_sinkLock);
+ if (!m_sink->SoftSuspend())
+ {
+ sinkIsSuspended = false; //sink cannot be suspended
+ m_softSuspend = false; //break suspend loop
+ break;
+ }
+ else
+ sinkIsSuspended = true; //sink has suspended processing
+ sinkLock.Leave();
+ }
+
+ /* idle for platform-defined time */
+ m_wake.WaitMSec(SOFTAE_IDLE_WAIT_MSEC);
+
+ /* check if we need to resume for stream or sound */
+ if (!m_isSuspended && (!m_playingStreams.empty() || !m_playing_sounds.empty()))
+ {
@arnova Collaborator
arnova added a note

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

@DDDamian Collaborator
DDDamian added a note

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
+ m_reOpen = !m_sink->SoftResume(); // sink returns false if it requires reinit
+ sinkIsSuspended = false; //sink processing data
+ m_softSuspend = false; //break suspend loop
+ break;
+ }
+ }
+}
+
View
9 xbmc/cores/AudioEngine/Engines/SoftAE/SoftAE.h
@@ -120,6 +120,8 @@ class CSoftAE : public IThreadedAE
bool SetupEncoder(AEAudioFormat &format);
void Deinitialize();
+ inline void ProcessSuspend(); /* enter suspend state if nothing to play and sink allows */
+
inline void GetDeviceFriendlyName(std::string &device);
IAESink *GetSink(AEAudioFormat &desiredFormat, bool passthrough, std::string &device);
@@ -133,9 +135,10 @@ class CSoftAE : public IThreadedAE
bool m_stereoUpmix;
/* internal vars */
- bool m_running, m_reOpen, m_isSuspended;
- bool m_softSuspend; /* latches after last stream or sound played for timer below */
- unsigned int m_softSuspendTimer; /* time in milliseconds to hold sink open before soft suspend */
+ bool m_running, m_reOpen;
+ bool m_isSuspended; /* engine suspended by external function to release audio context */
+ bool m_softSuspend; /* latches after last stream or sound played for timer below for idle */
+ unsigned int m_softSuspendTimer; /* time in milliseconds to hold sink open before soft suspend for idle */
CEvent m_reOpenEvent;
CEvent m_wake;
View
12 xbmc/cores/AudioEngine/Interfaces/AESink.h
@@ -87,5 +87,17 @@ class IAESink
This method sets the volume control, volume ranges from 0.0 to 1.0.
*/
virtual void SetVolume(float volume) {};
+
+ /*
+ Requests sink to prepare itself for a suspend state
+ @return false if sink cannot be suspended
+ */
+ virtual bool SoftSuspend() {return false;};
+
@DDDamian 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
+ /*
+ Notify sink to prepare to resume processing after suspend state
+ @return false if sink must be reinitialized
+ */
+ virtual bool SoftResume() {return false;};
};
View
6 xbmc/cores/AudioEngine/Sinks/AESinkALSA.cpp
@@ -489,9 +489,6 @@ unsigned int CAESinkALSA::AddPackets(uint8_t *data, unsigned int frames, bool ha
if (!m_pcm)
return 0;
- if (snd_pcm_state(m_pcm) == SND_PCM_STATE_PREPARED)
- snd_pcm_start(m_pcm);
-
int ret;
ret = snd_pcm_avail(m_pcm);
@@ -520,6 +517,9 @@ unsigned int CAESinkALSA::AddPackets(uint8_t *data, unsigned int frames, bool ha
}
}
+ if ( ret > 0 && snd_pcm_state(m_pcm) == SND_PCM_STATE_PREPARED)
+ snd_pcm_start(m_pcm);
+
return ret;
}
View
24 xbmc/cores/AudioEngine/Sinks/AESinkWASAPI.cpp
@@ -331,7 +331,8 @@ void CAESinkWASAPI::Deinitialize()
{
try
{
- m_pAudioClient->Stop();
+ m_pAudioClient->Stop(); //stop the audio output
+ m_pAudioClient->Reset(); //flush buffer and reset audio clock stream position
}
catch (...)
{
@@ -537,6 +538,27 @@ unsigned int CAESinkWASAPI::AddPackets(uint8_t *data, unsigned int frames, bool
return NumFramesRequested;
}
+bool CAESinkWASAPI::SoftSuspend()
+{
+ /* Sink has been asked to suspend output - we release audio */
+ /* device as we are in exclusive mode and thus allow external */
+ /* audio sources to play. This requires us to reinitialize */
+ /* on resume. */
+
+ Deinitialize();
+
+ return true;
+}
+
+bool CAESinkWASAPI::SoftResume()
+{
+ /* Sink asked to resume output. To release audio device in */
+ /* exclusive mode we release the device context and therefore */
+ /* must reinitialize. Return false to force re-init by engine */
+
+ return false;
+}
+
void CAESinkWASAPI::EnumerateDevicesEx(AEDeviceInfoList &deviceInfoList)
{
IMMDeviceEnumerator* pEnumerator = NULL;
View
3  xbmc/cores/AudioEngine/Sinks/AESinkWASAPI.h
@@ -43,6 +43,8 @@ class CAESinkWASAPI : public IAESink
virtual double GetCacheTime ();
virtual double GetCacheTotal ();
virtual unsigned int AddPackets (uint8_t *data, unsigned int frames, bool hasAudio);
+ virtual bool SoftSuspend ();
+ virtual bool SoftResume ();
static void EnumerateDevicesEx (AEDeviceInfoList &deviceInfoList);
private:
bool InitializeExclusive(AEAudioFormat &format);
@@ -70,6 +72,7 @@ class CAESinkWASAPI : public IAESink
bool m_running;
bool m_initialized;
+ bool m_isSuspended; /* sink is in a suspended state - release audio device */
bool m_isDirty; /* sink output failed - needs re-init or new device */
unsigned int m_uiBufferLen; /* wasapi endpoint buffer size, in frames */
Something went wrong with that request. Please try again.