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

Volume scale broken after changing the output device #801

Open
luzip665 opened this issue Jan 21, 2022 · 10 comments · Fixed by #803 · May be fixed by #805
Open

Volume scale broken after changing the output device #801

luzip665 opened this issue Jan 21, 2022 · 10 comments · Fixed by #803 · May be fixed by #805

Comments

@luzip665
Copy link
Contributor

Right after starting exaile I can scale the volume with the volume scaler and see the change on pulseaudio.

After I change the output device via Settings > Playback > Audio Device it is broken. When I scale in exaile nothing happens to pulseaudio.

Steps to Reproduce (for bugs)

  1. Change output device in Settings > Playback > Audio Device
  2. Scale volumne

Environment

  • Operating System and version: Ubuntu 21.10
  • Exaile Version: Current head
luzip665 added a commit to luzip665/exaile that referenced this issue Jan 24, 2022
luzip665 added a commit to luzip665/exaile that referenced this issue Jan 31, 2022
@luzip665 luzip665 linked a pull request Jan 31, 2022 that will close this issue
@virtuald
Copy link
Member

This definitely didn't used to be the case. Can you check an older version of exaile (4?) to see if this behavior is present?

@luzip665
Copy link
Contributor Author

Latest version I got working is 4.1.0. In this version it is the same.

Volume control works after click on stop and start again. It fails only if I change the device during playback.

@luzip665 luzip665 reopened this Jan 31, 2022
@virtuald
Copy link
Member

If a prior version wasn't broken, then it seems to me better to use git bisect to pinpoint what change broke it, and address the issue that way.

@luzip665
Copy link
Contributor Author

You are right, but actually I got only 4.1.0 running. There it is the same.
I hardly try to run a 4.0.x

@luzip665
Copy link
Contributor Author

luzip665 commented Jan 31, 2022

Updated summary (I hope this is more clear. Sorry for the misleading summary above):

It works as expected if I

  • play a song
  • stop it
  • change the device
  • start playback

It does not work if I

  • play the song
  • change the device
  • In this case the playback continues directly using the new device. Only volume control doesn't work further. It works after stopping the track and play again.

Unfortunately I'm not able to run Exaile 4.0.x because of outdated packages. But I think there should be no difference, because the code is from 2015.

I could imagine, that this thing wasn't noticed before.
Can you reproduce?

luzip665 added a commit to luzip665/exaile that referenced this issue Feb 1, 2022
@virtuald
Copy link
Member

virtuald commented Feb 2, 2022

67fda42 will need to be reverted, as it completely breaks switching audio streams for me.

If you use the gstreamer graphviz debugging stuff mentioned in the devel docs (you can use this in the simple console)

from xl.player import PLAYER
from gi.repository import Gst
Gst.debug_bin_to_dot_file(PLAYER._engine.main_stream.playbin, Gst.DebugGraphDetails.ALL, "filename")

You can see from the output image that the playbin and the playsink both have the correct volume after the sink change, but the audiosink is stuck at whatever volume the parent had.

Referring to https://github.com/GStreamer/gst-plugins-base/blob/ce937bcb21412d7b3539a2da0509cc96260562f8/gst/playback/gstplaysink.c#L2808, it looks like the playsink is looking for an element that implements GstStreamVolume, and then selects that as the element to send volume controls to. However, that's the element that we are replacing -- so likely when we switch the element out, it's still sending volume commands to the old sink, which isn't going to work.

I spent a very long time rewriting Exaile's audio engine and testing it, and I find it difficult to believe that I didn't catch this case. However, it seems that at least in some cases (see https://github.com/GStreamer/gst-plugins-base/blob/ce937bcb21412d7b3539a2da0509cc96260562f8/gst/playback/gstplaysink.c#L2870) the playsink will create its own volume element and use that instead -- in which case it might work on some computers and not on others with a different audio configuration (or differ based on whether alsa or pulseaudio is used). Maybe that's why I didn't catch it. In any case, it's definitely broken right now, thanks for finding that.

Now, how do we fix it?

It doesn't seem like there's a way to tell the playsink to send volume to a different element... which, makes sense I suppose, since it isn't designed to allow you to change the sink at runtime. After all, this is why we have our DynamicAudioSink element. I think we have two options:

  • Find a pre-existing element (or container element) that can forward volume changes to another element, and set things up so that we change the forwarding when we change the sink
  • The dynamic audio sink has an extra 'identity' element used for passing data through so that we don't have to do any kind of relinking, we could extend the identity element, implement GstStreamVolume, and do our own forwarding to the underlying audio sink

I doubt that there is a pre-existing element that does what we need, so the second option is probably our best bet. It might be worth looking at Quod Libet or other gst/python applications to see if they already have an element that does this sort of thing too. ... though I just went and glanced at QL, and they basically implement their own playbin in python, so they don't have to deal with this problem in the same way.

Unfortunately, I have to go to bed now, but I'll think about this.

@luzip665
Copy link
Contributor Author

luzip665 commented Feb 2, 2022

Thank you for investigating so deep!
I'll read the links now, especially about debugging gstreamer.

67fda42 is reverted.

@virtuald
Copy link
Member

virtuald commented Feb 2, 2022

There's a comment in the dynamic sink with a link about how the pipeline connection/disconnection works, it's really useful to read if you want to understand more about gstreamer: https://gstreamer.freedesktop.org/documentation/application-development/advanced/pipeline-manipulation.html?gi-language=python#dynamically-changing-the-pipeline

I feel like I read somewhere that they had implemented dynamic sink switching in playbin3, in which case none of this nonsense would be needed anymore. I haven't looked into that however, and I'm not sure if it's supported in the oldest version of gstreamer that we currently support.

Thanks for your help!

@virtuald
Copy link
Member

virtuald commented Feb 5, 2022

Hm, apparently Gst.Bin.get_by_interface finds the last element in the bin, which makes option #2 no good. Here's an initial stab at the forwarding element:

# TODO: doesn't seem to be a way to just import GstIdentity
identity = Gst.ElementFactory.make('identity')
GstIdentity = type(identity)
del identity


class VolumeForwardingElement(GstIdentity, GstAudio.StreamVolume):
    elem: typing.Optional[GstAudio.StreamVolume] = None

    @GObject.Property(type=bool, default=False)
    def mute(self) -> bool:
        print("getting")
        if self.elem is not None:
            return self.elem.props.mute
        return False

    @mute.setter
    def mute(self, v: bool):
        print("setting", v)
        if self.elem is not None:
            self.elem.props.mute = v

    @GObject.Property(type=float, default=0.0)
    def volume(self) -> float:
        print("getting")
        if self.elem is not None:
            return self.elem.props.volume
        return 0.0

    @volume.setter
    def volume(self, v: float):
        print("setting", v)
        if self.elem is not None:
            self.elem.props.volume = v


GObject.type_register(VolumeForwardingElement)

If I add it to the bin, and call print(Gst.Bin.get_by_interface(self, GstAudio.StreamVolume)) it does find my element. Unfortunately, if it's there, and the sink is there, it always finds the sink instead.

.. maybe we could add another bin inside the bin? I'm done with this for tonight.

@luzip665
Copy link
Contributor Author

luzip665 commented Feb 6, 2022

I had a look at Quod Libet and found https://github.com/quodlibet/quodlibet/blob/master/quodlibet/player/gstbe/player.py#L472.
They are using the volume element of the sink directly instead the volume element of the playbin, right?

So I changed only this in AudioStream class. Seems to work for now. May this be a possible way?

#805

luzip665 added a commit to luzip665/exaile that referenced this issue Feb 7, 2022
luzip665 added a commit to luzip665/exaile that referenced this issue Feb 7, 2022
luzip665 added a commit to luzip665/exaile that referenced this issue Feb 7, 2022
luzip665 added a commit to luzip665/exaile that referenced this issue Feb 7, 2022
luzip665 pushed a commit to luzip665/exaile that referenced this issue Feb 7, 2022
luzip665 added a commit to luzip665/exaile that referenced this issue Feb 7, 2022
luzip665 added a commit to luzip665/exaile that referenced this issue Feb 11, 2022
luzip665 added a commit to luzip665/exaile that referenced this issue Feb 11, 2022
luzip665 added a commit to luzip665/exaile that referenced this issue Feb 12, 2022
luzip665 added a commit to luzip665/exaile that referenced this issue Feb 12, 2022
luzip665 added a commit to luzip665/exaile that referenced this issue Feb 18, 2022
luzip665 added a commit to luzip665/exaile that referenced this issue Feb 18, 2022
luzip665 added a commit to luzip665/exaile that referenced this issue Feb 20, 2022
luzip665 added a commit to luzip665/exaile that referenced this issue Feb 20, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
2 participants