Merge branch 'develop' into feature/encapsulate-gstreamer-state-changes

Conflicts:
	mopidy/gstreamer.py
This commit is contained in:
Thomas Adamcik 2011-05-05 20:22:27 +02:00
commit 03c30369b3
9 changed files with 231 additions and 100 deletions

View File

@ -2,19 +2,15 @@
Output API
**********
Outputs are responsible for playing audio.
Outputs are used by :mod:`mopidy.gstreamer` to output audio in some way.
.. warning::
A stable output API is not available yet, as we've only implemented a
single output module.
.. automodule:: mopidy.outputs.base
:synopsis: Base class for outputs
.. autoclass:: mopidy.outputs.BaseOutput
:members:
Output implementations
======================
* :mod:`mopidy.outputs.gstreamer`
* :class:`mopidy.outputs.LocalOutput`
* :class:`mopidy.outputs.NullOutput`
* :class:`mopidy.outputs.ShoutcastOutput`

View File

@ -10,9 +10,16 @@ This change log is used to track all major changes to Mopidy.
No description yet.
**Important changes**
- Mopidy now supports running with 1-n outputs at the same time. This feature
was mainly added to facilitate Shoutcast support, which Mopidy has also
gained. In its current state outputs can not be toggled during runtime.
**Changes**
No changes yet.
- Fix local backend time query errors that where coming from stopped pipeline.
(Fixes: :issue:`87`)
0.4.0 (2011-04-27)

View File

@ -0,0 +1,9 @@
********************************************
:mod:`mopidy.gstreamer` -- GStreamer adapter
********************************************
.. inheritance-diagram:: mopidy.gstreamer
.. automodule:: mopidy.gstreamer
:synopsis: GStreamer adapter
:members:

9
docs/modules/outputs.rst Normal file
View File

@ -0,0 +1,9 @@
************************************************
:mod:`mopidy.outputs` -- GStreamer audio outputs
************************************************
.. inheritance-diagram:: mopidy.outputs
.. automodule:: mopidy.outputs
:synopsis: GStreamer audio outputs
:members:

View File

@ -1,9 +0,0 @@
*********************************************************************
:mod:`mopidy.outputs.gstreamer` -- GStreamer output for all platforms
*********************************************************************
.. inheritance-diagram:: mopidy.outputs.gstreamer
.. automodule:: mopidy.outputs.gstreamer
:synopsis: GStreamer output for all platforms
:members:

View File

@ -22,46 +22,6 @@ default_caps = gst.Caps("""
signed=(boolean)true,
rate=(int)44100""")
class BaseOutput(object):
def connect_bin(self, pipeline, element_to_link_to):
"""
Connect output bin to pipeline and given element.
"""
description = 'queue ! %s' % self.describe_bin()
logger.debug('Adding new output to tee: %s', description)
output = self.parse_bin(description)
self.modify_bin(output)
pipeline.add(output)
output.sync_state_with_parent()
gst.element_link_many(element_to_link_to, output)
def parse_bin(self, description):
return gst.parse_bin_from_description(description, True)
def modify_bin(self, output):
"""
Modifies bin before it is installed if needed
"""
pass
def describe_bin(self):
"""
Describe bin to be parsed.
Must be implemented by subclasses.
"""
raise NotImplementedError
def set_properties(self, element, properties):
"""
Set properties on element if they have a value.
"""
for key, value in properties.items():
if value:
element.set_property(key, value)
class GStreamer(ThreadingActor):
"""
@ -69,7 +29,7 @@ class GStreamer(ThreadingActor):
**Settings:**
- :attr:`mopidy.settings.GSTREAMER_AUDIO_SINK`
- :attr:`mopidy.settings.OUTPUTS`
"""
@ -81,9 +41,9 @@ class GStreamer(ThreadingActor):
def _setup_gstreamer(self):
"""
**Warning:** :class:`GStreamerOutput` requires
**Warning:** :class:`GStreamer` requires
:class:`mopidy.utils.process.GObjectEventThread` to be running. This is
not enforced by :class:`GStreamerOutput` itself.
not enforced by :class:`GStreamer` itself.
"""
base_pipeline = ' ! '.join([
'audioconvert name=convert',
@ -145,13 +105,19 @@ class GStreamer(ThreadingActor):
return backend_refs[0].proxy()
def set_uri(self, uri):
"""Play audio at URI"""
"""Change internal uridecodebin's URI"""
self.gst_uridecodebin.set_property('uri', uri)
def deliver_data(self, caps_string, data):
"""Deliver audio data to be played"""
def deliver_data(self, capabilities, data):
"""
Deliver audio data to be played
:param capabilities: a GStreamer capabilities string
:type capabilities: string
:param data: raw audio data to be played
"""
source = self.gst_pipeline.get_by_name('source')
caps = gst.caps_from_string(caps_string)
caps = gst.caps_from_string(capabilities)
buffer_ = gst.Buffer(buffer(data))
buffer_.set_caps(caps)
source.set_property('caps', caps)
@ -167,6 +133,11 @@ class GStreamer(ThreadingActor):
self.gst_pipeline.get_by_name('source').emit('end-of-stream')
def get_position(self):
"""
Get position in milliseconds.
:rtype: int
"""
if self.gst_pipeline.get_state()[1] == gst.STATE_NULL:
return 0
try:
@ -177,6 +148,13 @@ class GStreamer(ThreadingActor):
return 0
def set_position(self, position):
"""
Set position in milliseconds.
:param position: the position in milliseconds
:type volume: int
:rtype: :class:`True` if successful, else :class:`False`
"""
self.gst_pipeline.get_state() # block until state changes are done
handeled = self.gst_pipeline.seek_simple(gst.Format(gst.FORMAT_TIME),
gst.SEEK_FLAG_FLUSH, position * gst.MSECOND)
@ -232,15 +210,35 @@ class GStreamer(ThreadingActor):
return True
def get_volume(self):
"""Get volume in range [0..100]"""
"""
Get volume level for software mixer.
:rtype: int in range [0..100]
"""
return int(self.gst_volume.get_property('volume') * 100)
def set_volume(self, volume):
"""Set volume in range [0..100]"""
"""
Set volume level for software mixer.
:param volume: the volume in the range [0..100]
:type volume: int
:rtype: :class:`True` if successful, else :class:`False`
"""
self.gst_volume.set_property('volume', volume / 100.0)
return True
def set_metadata(self, track):
"""
Set track metadata for currently playing song.
Only needs to be called by sources such as appsrc which don't already
inject tags in pipeline.
:param track: Track containing metadata for current song.
:type track: :class:`mopidy.modes.Track`
"""
# FIXME what if we want to unset taginject tags?
tags = u'artist="%(artist)s",title="%(title)s"' % {
'artist': u', '.join([a.name for a in track.artists]),
'title': track.name,

View File

@ -1,31 +0,0 @@
from mopidy import settings
from mopidy.gstreamer import BaseOutput
class LocalOutput(BaseOutput):
def describe_bin(self):
if settings.LOCAL_OUTPUT_OVERRIDE:
return settings.LOCAL_OUTPUT_OVERRIDE
return 'autoaudiosink'
class NullOutput(BaseOutput):
def describe_bin(self):
return 'fakesink'
class ShoutcastOutput(BaseOutput):
def describe_bin(self):
if settings.SHOUTCAST_OUTPUT_OVERRIDE:
return settings.SHOUTCAST_OUTPUT_OVERRIDE
return 'audioconvert ! %s ! shout2send name=shoutcast' \
% settings.SHOUTCAST_OUTPUT_ENCODER
def modify_bin(self, output):
if settings.SHOUTCAST_OUTPUT_OVERRIDE:
return
self.set_properties(output.get_by_name('shoutcast'), {
u'ip': settings.SHOUTCAST_OUTPUT_SERVER,
u'mount': settings.SHOUTCAST_OUTPUT_MOUNT,
u'port': settings.SHOUTCAST_OUTPUT_PORT,
u'username': settings.SHOUTCAST_OUTPUT_USERNAME,
u'password': settings.SHOUTCAST_OUTPUT_PASSWORD,
})

152
mopidy/outputs/__init__.py Normal file
View File

@ -0,0 +1,152 @@
import logging
import pygst
pygst.require('0.10')
import gst
from mopidy import settings
logger = logging.getLogger('mopidy.outputs')
class BaseOutput(object):
"""Base class for providing support for multiple pluggable outputs."""
def connect_bin(self, pipeline, element):
"""
Connect output bin to pipeline and given element.
In normal cases the element will probably be a `tee`,
thus allowing us to connect any number of outputs. This
however is why each bin is forced to have its own `queue`
after the `tee`.
:param pipeline: gst.Pipeline to add output to.
:type pipeline: :class:`gst.Pipeline`
:param element: gst.Element in pipeline to connect output to.
:type element: :class:`gst.Element`
"""
description = 'queue ! %s' % self.describe_bin()
logger.debug('Adding new output to tee: %s', description)
output = gst.parse_bin_from_description(description, True)
self.modify_bin(output)
pipeline.add(output)
output.sync_state_with_parent() # Required to add to running pipe
gst.element_link_many(element, output)
def modify_bin(self, output):
"""
Modifies bin before it is installed if needed.
Overriding this method allows for outputs to modify the constructed bin
before it is installed. This can for instance be a good place to call
`set_properties` on elements that need to be configured.
:param output: gst.Bin to modify in some way.
:type output: :class:`gst.Bin`
"""
pass
def describe_bin(self):
"""
Return text string describing bin in gst-launch format.
For simple cases this can just be a plain sink such as `autoaudiosink`
or it can be a chain `element1 ! element2 ! sink`. See `man
gst-launch0.10` for details on format.
*MUST be implemented by subclass.*
"""
raise NotImplementedError
def set_properties(self, element, properties):
"""
Helper to allow for simple setting of properties on elements.
Will call `set_property` on the element for each key that has a value
that is not None.
:param element: gst.Element to set properties on.
:type element: :class:`gst.Element`
:param properties: Dictionary of properties to set on element.
:type properties: dict
"""
for key, value in properties.items():
if value is not None:
element.set_property(key, value)
class LocalOutput(BaseOutput):
"""
Basic output to local audio sink.
This output will normally tell GStreamer to choose whatever it thinks is
best for your system. In other words this is usually a sane choice.
Advanced:
However, there are chases when you want to explicitly set what GStreamer
should use. This can be achieved by setting `settings.LOCAL_OUTPUT_OVERRIDE`
to the sink you want to use. Some of the possible values are: alsasink,
esdsink, jackaudiosink, oss4sink, osssink and pulsesink. Exact values that
will work on your system will depend on your sound setup and installed
GStreamer plugins. Run `gst-inspect0.10` for list of all available plugins.
Also note that this accepts properties and bins in `gst-launch` format.
"""
def describe_bin(self):
if settings.LOCAL_OUTPUT_OVERRIDE:
return settings.LOCAL_OUTPUT_OVERRIDE
return 'autoaudiosink'
class NullOutput(BaseOutput):
"""
Fall-back null output.
This output will not output anything. It is intended as a fall-back for
when setup of all other outputs have failed and should not be used by end
users. Inserting this output in such a case ensures that the pipeline does
not crash.
"""
def describe_bin(self):
return 'fakesink'
class ShoutcastOutput(BaseOutput):
"""
Shoutcast streaming output.
This output allows for streaming to an icecast server or anything else that
supports Shoutcast. The output supports setting for: server address, port,
mount point, user, password and encoder to use. Please see
:class:`mopidy.settings` for details about settings.
Advanced:
If you need to do something special that this output has not taken into
account the setting `settings.SHOUTCAST_OUTPUT_OVERRIDE` has been provided
to allow for manual setup of the bin using a gst-launch string. If this
setting is set all other shoutcast settings will be ignored.
"""
def describe_bin(self):
if settings.SHOUTCAST_OUTPUT_OVERRIDE:
return settings.SHOUTCAST_OUTPUT_OVERRIDE
return 'audioconvert ! %s ! shout2send name=shoutcast' \
% settings.SHOUTCAST_OUTPUT_ENCODER
def modify_bin(self, output):
if settings.SHOUTCAST_OUTPUT_OVERRIDE:
return
self.set_properties(output.get_by_name('shoutcast'), {
u'ip': settings.SHOUTCAST_OUTPUT_SERVER,
u'mount': settings.SHOUTCAST_OUTPUT_MOUNT,
u'port': settings.SHOUTCAST_OUTPUT_PORT,
u'username': settings.SHOUTCAST_OUTPUT_USERNAME,
u'password': settings.SHOUTCAST_OUTPUT_PASSWORD,
})

View File

@ -54,7 +54,7 @@ FRONTENDS = (
u'mopidy.frontends.lastfm.LastfmFrontend',
)
#: Which GStreamer bin description to use in :mod:`mopidy.outputs.CustomOutput`.
#: Which GStreamer bin description to use in :class:`mopidy.outputs.LocalOutput`.
#:
#: Default::
#: