audio: Split out ouput handling from audio actor

This also lays some basic ground work for handling multiple outputs.
This commit is contained in:
Thomas Adamcik 2014-08-03 21:46:37 +02:00
parent b9879ef81e
commit d650c0ba14
3 changed files with 66 additions and 31 deletions

View File

@ -11,6 +11,7 @@ import gst.pbutils
import pykka
from mopidy import exceptions
from mopidy.audio import playlists, utils
from mopidy.audio.constants import PlaybackState
from mopidy.audio.listener import AudioListener
@ -141,6 +142,56 @@ class _Appsrc(object):
return True
class _Outputs(gst.Bin):
def __init__(self):
gst.Bin.__init__(self)
self._tee = gst.element_factory_make('tee')
self.add(self._tee)
# Queue element to buy us time between the about to finish event and
# the actual switch, i.e. about to switch can block for longer thanks
# to this queue.
# TODO: make the min-max values a setting?
# TODO: move out of this class?
queue = gst.element_factory_make('queue')
queue.set_property('max-size-buffers', 0)
queue.set_property('max-size-bytes', 0)
queue.set_property('max-size-time', 5 * gst.SECOND)
queue.set_property('min-threshold-time', 3 * gst.SECOND)
self.add(queue)
queue.link(self._tee)
ghost_pad = gst.GhostPad('sink', queue.get_pad('sink'))
self.add_pad(ghost_pad)
# Add an always connected fakesink so the tee doesn't fail.
# XXX disabled for now as we get one stream changed per sink...
# self._add(gst.element_factory_make('fakesink'))
def add_output(self, description):
# XXX This only works for pipelines not in use until #790 gets done.
try:
output = gst.parse_bin_from_description(
description, ghost_unconnected_pads=True)
except gobject.GError as ex:
logger.error(
'Failed to create audio output "%s": %s', description, ex)
raise exceptions.AudioException(bytes(ex))
self._add(output)
logger.info('Audio output set to "%s"', description)
def _add(self, element):
# All tee branches need a queue in front of them.
queue = gst.element_factory_make('queue')
self.add(element)
self.add(queue)
queue.link(element)
self._tee.link(queue)
# TODO: create a player class which replaces the actors internals
class Audio(pykka.ThreadingActor):
"""
@ -159,6 +210,7 @@ class Audio(pykka.ThreadingActor):
self._buffering = False
self._playbin = None
self._outputs = None
self._about_to_finish_callback = None
self._appsrc = _Appsrc()
@ -241,38 +293,13 @@ class Audio(pykka.ThreadingActor):
self._playbin.set_state(gst.STATE_NULL)
def _setup_output(self):
output_desc = self._config['audio']['output']
self._outputs = _Outputs()
self._outputs.get_pad('sink').add_event_probe(self._on_pad_event)
try:
user_output = gst.parse_bin_from_description(
output_desc, ghost_unconnected_pads=True)
except gobject.GError as ex:
logger.error(
'Failed to create audio output "%s": %s', output_desc, ex)
process.exit_process()
output = gst.Bin('output')
# Queue element to buy us time between the about to finish event and
# the actual switch, i.e. about to switch can block for longer thanks
# to this queue.
# TODO: make the min-max values a setting?
queue = gst.element_factory_make('queue')
queue.set_property('max-size-buffers', 0)
queue.set_property('max-size-bytes', 0)
queue.set_property('max-size-time', 5 * gst.SECOND)
queue.set_property('min-threshold-time', 3 * gst.SECOND)
queue.get_pad('src').add_event_probe(self._on_pad_event)
output.add(user_output)
output.add(queue)
queue.link(user_output)
ghost_pad = gst.GhostPad('sink', queue.get_pad('sink'))
output.add_pad(ghost_pad)
logger.info('Audio output set to "%s"', output_desc)
self._playbin.set_property('audio-sink', output)
self._outputs.add_output(self._config['audio']['output'])
except exceptions.AudioException:
process.exit_process() # TODO: move this up the chain
self._playbin.set_property('audio-sink', self._outputs)
def _setup_mixer(self):
if self._config['audio']['mixer'] != 'software':

View File

@ -34,3 +34,7 @@ class MixerError(MopidyException):
class ScannerError(MopidyException):
pass
class AudioException(MopidyException):
pass

View File

@ -31,3 +31,7 @@ class ExceptionsTest(unittest.TestCase):
def test_scanner_error_is_a_mopidy_exception(self):
self.assert_(issubclass(
exceptions.ScannerError, exceptions.MopidyException))
def test_audio_error_is_a_mopidy_exception(self):
self.assert_(issubclass(
exceptions.AudioException, exceptions.MopidyException))