Convert to only using GStreamer mixers.
This commit is contained in:
parent
915130e352
commit
6e3e1f997f
@ -106,10 +106,12 @@ def stop_gstreamer():
|
||||
stop_actors_by_class(GStreamer)
|
||||
|
||||
def setup_mixer():
|
||||
get_class(settings.MIXER).start()
|
||||
# TODO: remove this hack which is just a stepping stone for our
|
||||
# refactoring.
|
||||
get_class('mopidy.mixers.gstreamer_software.GStreamerSoftwareMixer').start()
|
||||
|
||||
def stop_mixer():
|
||||
stop_actors_by_class(get_class(settings.MIXER))
|
||||
stop_actors_by_class(get_class('mopidy.mixers.gstreamer_software.GStreamerSoftwareMixer'))
|
||||
|
||||
def setup_backend():
|
||||
get_class(settings.BACKENDS[0]).start()
|
||||
|
||||
@ -37,13 +37,17 @@ class GStreamer(ThreadingActor):
|
||||
self._source = None
|
||||
self._uridecodebin = None
|
||||
self._output = None
|
||||
self._mixer = None
|
||||
|
||||
def on_start(self):
|
||||
self._setup_pipeline()
|
||||
self._setup_output()
|
||||
self._setup_mixer()
|
||||
self._setup_message_processor()
|
||||
|
||||
def _setup_pipeline(self):
|
||||
# TODO: replace with and input bin so we simply have an input bin we
|
||||
# connect to an output bin with a mixer on the side. set_uri on bin?
|
||||
description = ' ! '.join([
|
||||
'uridecodebin name=uri',
|
||||
'audioconvert name=convert'])
|
||||
@ -64,6 +68,36 @@ class GStreamer(ThreadingActor):
|
||||
self._output)
|
||||
logger.debug('Output set to %s', settings.OUTPUT)
|
||||
|
||||
def _setup_mixer(self):
|
||||
if not settings.MIXER:
|
||||
logger.debug('Not adding mixer.')
|
||||
return
|
||||
|
||||
mixer = gst.element_factory_make(settings.MIXER)
|
||||
if mixer.set_state(gst.STATE_READY) != gst.STATE_CHANGE_SUCCESS:
|
||||
logger.warning('Adding mixer %r failed.', settings.MIXER)
|
||||
return
|
||||
|
||||
track = self._select_mixer_track(mixer)
|
||||
if not track:
|
||||
logger.warning('Could not find usable mixer track.')
|
||||
return
|
||||
|
||||
self._mixer = (mixer, track)
|
||||
logger.info('Mixer set to %s using %s',
|
||||
mixer.get_factory().get_name(), track.label)
|
||||
|
||||
def _select_mixer_track(self, mixer):
|
||||
# Look for track with label == MIXER_TRACK, otherwise fallback to
|
||||
# master track which is also an output.
|
||||
for track in mixer.list_tracks():
|
||||
if settings.MIXER_TRACK:
|
||||
if track.label == settings.MIXER_TRACK:
|
||||
return track
|
||||
elif track.flags & (gst.interfaces.MIXER_TRACK_MASTER |
|
||||
gst.interfaces.MIXER_TRACK_OUTPUT):
|
||||
return track
|
||||
|
||||
def _setup_message_processor(self):
|
||||
bus = self._pipeline.get_bus()
|
||||
bus.add_signal_watch()
|
||||
@ -236,33 +270,41 @@ class GStreamer(ThreadingActor):
|
||||
|
||||
def get_volume(self):
|
||||
"""
|
||||
Get volume level of the GStreamer software mixer.
|
||||
Get volume level of the installed mixer.
|
||||
|
||||
:rtype: int in range [0..100]
|
||||
:rtype: int in range [-1..100]
|
||||
"""
|
||||
mixers = self._pipeline.iterate_all_by_interface(gst.interfaces.Mixer)
|
||||
try:
|
||||
mixer = mixers.next()
|
||||
except StopIteration:
|
||||
return 0
|
||||
# FIXME this _will_ break for mixers that don't implement
|
||||
# GstStreamVolume
|
||||
return int(mixer.get_property('volume') * 100)
|
||||
if self._mixer is None:
|
||||
# TODO: add tests for this case and check we propagate change
|
||||
return -1
|
||||
|
||||
mixer, track = self._mixer
|
||||
|
||||
volumes = mixer.get_volume(track)
|
||||
avg_volume = sum(volumes) / len(volumes)
|
||||
return utils.rescale(avg_volume,
|
||||
old=(track.min_volume, track.max_volume),
|
||||
new=(0, 100))
|
||||
|
||||
def set_volume(self, volume):
|
||||
"""
|
||||
Set volume level of the GStreamer software mixer.
|
||||
Set volume level of the installed mixer.
|
||||
|
||||
:param volume: the volume in the range [0..100]
|
||||
:type volume: int
|
||||
:rtype: :class:`True` if successful, else :class:`False`
|
||||
"""
|
||||
mixers = self._pipeline.iterate_all_by_interface(gst.interfaces.Mixer)
|
||||
for mixer in mixers:
|
||||
# FIXME this _will_ break for mixers that don't implement
|
||||
# GstStreamVolume
|
||||
mixer.set_property('volume', volume / 100.0)
|
||||
return True
|
||||
if self._mixer is None:
|
||||
return False
|
||||
|
||||
mixer, track = self._mixer
|
||||
|
||||
volume = utils.rescale(volume, old=(0, 100),
|
||||
new=(track.min_volume, track.max_volume))
|
||||
volumes = (volume,) * track.num_channels
|
||||
|
||||
mixer.set_volume(track, volumes)
|
||||
return mixer.get_volume(track) == volumes
|
||||
|
||||
def set_metadata(self, track):
|
||||
"""
|
||||
|
||||
@ -103,40 +103,28 @@ LOCAL_PLAYLIST_PATH = None
|
||||
#: LOCAL_TAG_CACHE_FILE = None # Implies $XDG_DATA_DIR/mopidy/tag_cache
|
||||
LOCAL_TAG_CACHE_FILE = None
|
||||
|
||||
#: Sound mixer to use. See :mod:`mopidy.mixers` for all available mixers.
|
||||
#: Sound mixer to use.
|
||||
#:
|
||||
#: Expects a GStreamer mixer to use, typical values are:
|
||||
#: alsamixer, pulsemixer, oss4mixer, ossmixer.
|
||||
#:
|
||||
#: Setting this to ``None`` means no volume controll.
|
||||
#:
|
||||
#: Default::
|
||||
#:
|
||||
#: MIXER = u'mopidy.mixers.gstreamer_software.GStreamerSoftwareMixer'
|
||||
MIXER = u'mopidy.mixers.gstreamer_software.GStreamerSoftwareMixer'
|
||||
#: MIXER = u'alsamixer'
|
||||
# TODO: update to an automixer that tries to select correct mixer.
|
||||
MIXER = u'alsamixer'
|
||||
|
||||
#: ALSA mixer only. What mixer control to use. If set to :class:`False`, first
|
||||
#: ``Master`` and then ``PCM`` will be tried.
|
||||
#: Sound mixer track to use.
|
||||
#:
|
||||
#: Example: ``Master Front``. Default: :class:`False`
|
||||
MIXER_ALSA_CONTROL = False
|
||||
|
||||
#: External mixers only. Which port the mixer is connected to.
|
||||
#: Name of the mixer track to use. If this is not set we will try to find the
|
||||
#: output track with master set.
|
||||
#:
|
||||
#: This must point to the device port like ``/dev/ttyUSB0``.
|
||||
#: Default::
|
||||
#:
|
||||
#: Default: :class:`None`
|
||||
MIXER_EXT_PORT = None
|
||||
|
||||
#: External mixers only. What input source the external mixer should use.
|
||||
#:
|
||||
#: Example: ``Aux``. Default: :class:`None`
|
||||
MIXER_EXT_SOURCE = None
|
||||
|
||||
#: External mixers only. What state Speakers A should be in.
|
||||
#:
|
||||
#: Default: :class:`None`.
|
||||
MIXER_EXT_SPEAKERS_A = None
|
||||
|
||||
#: External mixers only. What state Speakers B should be in.
|
||||
#:
|
||||
#: Default: :class:`None`.
|
||||
MIXER_EXT_SPEAKERS_B = None
|
||||
#: MIXER_TRACK = None
|
||||
MIXER_TRACK = None
|
||||
|
||||
#: The maximum volume. Integer in the range 0 to 100.
|
||||
#:
|
||||
@ -146,6 +134,7 @@ MIXER_EXT_SPEAKERS_B = None
|
||||
#: Default::
|
||||
#:
|
||||
#: MIXER_MAX_VOLUME = 100
|
||||
# TODO: re-add support for this.
|
||||
MIXER_MAX_VOLUME = 100
|
||||
|
||||
#: Which address Mopidy's MPD server should bind to.
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
from __future__ import division
|
||||
|
||||
import locale
|
||||
import logging
|
||||
import os
|
||||
@ -17,6 +19,14 @@ def flatten(the_list):
|
||||
return result
|
||||
|
||||
|
||||
def rescale(v, old=None, new=None):
|
||||
"""Convert value between scales."""
|
||||
new_min, new_max = new
|
||||
old_min, old_max = old
|
||||
scaled = (new_max - new_min) / (old_max - old_min) * (v - old_min) + new_min
|
||||
return int(scaled)
|
||||
|
||||
|
||||
def import_module(name):
|
||||
__import__(name)
|
||||
return sys.modules[name]
|
||||
|
||||
@ -121,6 +121,10 @@ def validate_settings(defaults, settings):
|
||||
'LOCAL_OUTPUT_OVERRIDE': 'CUSTOM_OUTPUT',
|
||||
'LOCAL_PLAYLIST_FOLDER': 'LOCAL_PLAYLIST_PATH',
|
||||
'LOCAL_TAG_CACHE': 'LOCAL_TAG_CACHE_FILE',
|
||||
'MIXER_ALSA_CONTROL': None,
|
||||
'MIXER_EXT_PORT': None,
|
||||
'MIXER_EXT_SPEAKERS_A': None,
|
||||
'MIXER_EXT_SPEAKERS_B': None,
|
||||
'SERVER': None,
|
||||
'SERVER_HOSTNAME': 'MPD_SERVER_HOSTNAME',
|
||||
'SERVER_PORT': 'MPD_SERVER_PORT',
|
||||
|
||||
Loading…
Reference in New Issue
Block a user