audio: Refactor softwaremixer and audio interactions

This rips the mixer bits and pieces that have been hiding in the audio actor to
it's own class. The software mixer now only knows about this and nothing else
from audio.
This commit is contained in:
Thomas Adamcik 2014-08-03 23:46:05 +02:00
parent 4bfc5e7a80
commit b8a0ca59cd
2 changed files with 86 additions and 91 deletions

View File

@ -192,6 +192,53 @@ class _Outputs(gst.Bin):
self._tee.link(queue)
class SoftwareMixer(object):
pykka_traversable = True
def __init__(self, mixer):
self._mixer = mixer
self._element = None
self._last_volume = None
self._last_mute = None
self._signals = _Signals()
def setup(self, element, mixer_ref):
self._element = element
self._signals.connect(element, 'notify::volume', self._volume_changed)
self._signals.connect(element, 'notify::mute', self._mute_changed)
self._mixer.setup(mixer_ref)
def teardown(self):
self._signals.clear()
self._mixer.teardown()
def get_volume(self):
return int(round(self._element.get_property('volume') * 100))
def set_volume(self, volume):
self._element.set_property('volume', volume / 100.0)
def get_mute(self):
return self._element.get_property('mute')
def set_mute(self, mute):
return self._element.set_property('mute', bool(mute))
def _volume_changed(self, element, property_):
old_volume, self._last_volume = self._last_volume, self.get_volume()
if old_volume != self._last_volume:
gst_logger.debug('Notify volume: %s', self._last_volume / 100.0)
self._mixer.trigger_volume_changed(self._last_volume)
def _mute_changed(self, element, property_):
old_mute, self._last_mute = self._last_mute, self.get_mute()
if old_mute != self._last_mute:
gst_logger.debug('Notify mute: %s', self._last_mute)
self._mixer.trigger_mute_changed(self._last_mute)
def setup_proxy(element, config):
# TODO: reuse in scanner code
if not config.get('hostname'):
@ -215,11 +262,13 @@ class Audio(pykka.ThreadingActor):
#: The GStreamer state mapped to :class:`mopidy.audio.PlaybackState`
state = PlaybackState.STOPPED
#: The software mixing interface :class:`mopidy.audio.actor.SoftwareMixer`
mixer = None
def __init__(self, config, mixer):
super(Audio, self).__init__()
self._config = config
self._mixer = mixer
self._target_state = gst.STATE_NULL
self._buffering = False
@ -230,6 +279,9 @@ class Audio(pykka.ThreadingActor):
self._appsrc = _Appsrc()
self._signals = _Signals()
if mixer and self._config['audio']['mixer'] == 'software':
self.mixer = SoftwareMixer(mixer)
def on_start(self):
try:
self._setup_preferences()
@ -300,31 +352,12 @@ class Audio(pykka.ThreadingActor):
self._playbin.set_property('audio-sink', self._outputs)
def _setup_mixer(self):
if self._config['audio']['mixer'] != 'software':
return
self._mixer.audio = self.actor_ref.proxy()
self._signals.connect(
self._playbin, 'notify::volume', self._on_mixer_change)
self._signals.connect(
self._playbin, 'notify::mute', self._on_mixer_change)
# The Mopidy startup procedure will set the initial volume of a mixer,
# but this happens before the audio actor is injected into the software
# mixer and has no effect. Thus, we need to set the initial volume
# again.
initial_volume = self._config['audio']['mixer_volume']
if initial_volume is not None:
self._mixer.set_volume(initial_volume)
def _on_mixer_change(self, element, gparamspec):
self._mixer.trigger_events_for_changed_values()
if self.mixer:
self.mixer.setup(self._playbin, self.actor_ref.proxy().mixer)
def _teardown_mixer(self):
if self._config['audio']['mixer'] != 'software':
return
self._signals.disconnect(self._playbin, 'notify::volume')
self._signals.disconnect(self._playbin, 'notify::mute')
self._mixer.audio = None
if self.mixer:
self.mixer.teardown()
def _setup_visualizer(self):
visualizer_element = self._config['audio']['visualizer']
@ -647,52 +680,6 @@ class Audio(pykka.ThreadingActor):
# of faking it in the message handling when result=OK
return True
def get_volume(self):
"""
Get volume level of the software mixer.
Example values:
0:
Minimum volume.
100:
Maximum volume.
:rtype: int in range [0..100]
"""
return int(round(self._playbin.get_property('volume') * 100))
def set_volume(self, volume):
"""
Set volume level of the software mixer.
:param volume: the volume in the range [0..100]
:type volume: int
:rtype: :class:`True` if successful, else :class:`False`
"""
self._playbin.set_property('volume', volume / 100.0)
return True
def get_mute(self):
"""
Get mute status of the software mixer.
:rtype: :class:`True` if muted, :class:`False` if unmuted,
:class:`None` if no mixer is installed.
"""
return self._playbin.get_property('mute')
def set_mute(self, mute):
"""
Mute or unmute of the software mixer.
:param mute: Whether to mute the mixer or not.
:type mute: bool
:rtype: :class:`True` if successful, else :class:`False`
"""
self._playbin.set_property('mute', bool(mute))
return True
def set_metadata(self, track):
"""
Set track metadata for currently playing song.

View File

@ -17,40 +17,48 @@ class SoftwareMixer(pykka.ThreadingActor, mixer.Mixer):
def __init__(self, config):
super(SoftwareMixer, self).__init__(config)
self.audio = None
self._last_volume = None
self._last_mute = None
self._audio_mixer = None
self._initial_volume = None
self._initial_mute = None
# TODO: shouldn't this be logged by thing that choose us?
logger.info('Mixing using GStreamer software mixing')
def setup(self, mixer_ref):
self._audio_mixer = mixer_ref
# The Mopidy startup procedure will set the initial volume of a
# mixer, but this happens before the audio actor is injected into the
# software mixer and has no effect. Thus, we need to set the initial
# volume again.
if self._initial_volume is not None:
self.set_volume(self._initial_volume)
if self._initial_mute is not None:
self.set_mute(self._initial_mute)
def teardown(self):
self._audio_mixer = None
def get_volume(self):
if self.audio is None:
if self._audio_mixer is None:
return None
return self.audio.get_volume().get()
return self._audio_mixer.get_volume().get()
def set_volume(self, volume):
if self.audio is None:
if self._audio_mixer is None:
self._initial_volume = volume
return False
self.audio.set_volume(volume)
self._audio_mixer.set_volume(volume)
return True
def get_mute(self):
if self.audio is None:
if self._audio_mixer is None:
return None
return self.audio.get_mute().get()
return self._audio_mixer.get_mute().get()
def set_mute(self, mute):
if self.audio is None:
if self._audio_mixer is None:
self._initial_mute = mute
return False
self.audio.set_mute(mute)
self._audio_mixer.set_mute(mute)
return True
def trigger_events_for_changed_values(self):
old_volume, self._last_volume = self._last_volume, self.get_volume()
old_mute, self._last_mute = self._last_mute, self.get_mute()
if old_volume != self._last_volume:
self.trigger_volume_changed(self._last_volume)
if old_mute != self._last_mute:
self.trigger_mute_changed(self._last_mute)