Convert to only using GStreamer mixers.

This commit is contained in:
Thomas Adamcik 2012-08-26 14:30:13 +02:00
parent 915130e352
commit 6e3e1f997f
5 changed files with 93 additions and 46 deletions

View File

@ -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()

View File

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

View File

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

View File

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

View File

@ -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',