Transforms

From XMMS2
Jump to: navigation, search

Overview

A.K.A. "xforms", "transformations" or "decoder/transport API unification". Transforms are a unification of the transport, decoder, and effect plugin API's. To reduce confusion, the merging of the transport and decoder plugins is at an API level only. Under the hood, plugins will still fulfill the role of 'transport' or 'decoder'.

A transform is something that takes some kind of data and outputs another kind of data. For example:

  • mad - reads "audio/mpeg" and outputs "audio/pcm-data"
  • file - reads "application/x-url" and outputs "application/octet-stream"
  • eq - reads "audio/pcm-data" and outputs "audio/pcm-data"

The last example shows that you can even output the same data type, as is the case with effects.

How They Work

Each transform plugin has a stream type to define what data it accepts. And an xform (as in the xform object in the API) defines another stream type, which describes the type of data it outputs. This makes the instantiation of the chain of transforms look something along the lines:

goaltypes = output->supported_types; /* "audio/pcm-data" */
xform = xmms_xform_new (NULL);
xmms_xform_set_output_type (xform,
                            "mime", "application/x-url",
                            "url", url);
while (xmms_streamtype_match(xform->out_type, goaltypes)) {
    plugin = xmms_xform_plugin_find (xform->out_type);
    xform = xmms_xform_new (plugin);
}

The xform API aims to be as simple as possible; each transform plugin only has a few methods; init, destroy, read and seek. The read method is modelled after the read(2) syscall, and should read a number of bytes into a supplied buffer. The read method might call "xmms_xform_read" to read data from the previous xform in the chain.

A Step-By-Step Example

Say you wish to play file "file:///media/foo.ogg" with XMMS2, here's how the transform chain is built:

  1. A fake xform is built, which outputs a stream (which EOF's directly, no one ever reads from it) with type {'mime': 'application/x-url', 'url': 'file://media/foo.ogg'} (see xmms_output_filler() in src/xmms/output.c)
  2. The list of xform plugins is traversed for a plugin that can handle that type. (The file plugin will be selected as it has the input stream type constraint: {'mime': 'application/x-url', 'url': 'file://*'}) (see xmms_xform_chain_setup() in src/xmms/xform.c)
  3. A file xform is initialized from the plugin and the init-method is called. The init method tells that it can output data of type: {'mime': 'application/octet-stream'} (see xmms_file_plugin_setup() and xmms_file_init() in src/plugins/file/file.c)
  4. The list of xform plugins is traversed again for a plugin that can handle that type. (The magic plugin will be selected as it has the input stream type constraint: {'mime': 'application/octet-stream'}) (see xmms_magic_plugin_setup() in src/xmms/magic.c)
  5. The init-method of the magic xform does magic file detection and finds out that it is mimetype 'audio/oggvorbis' so the stream type will be {'mime': 'audio/oggvorbis'} (see xmms_magic_plugin_init() in src/xmms/magic.c)
  6. The list of xform plugins is traversed again for a plugin that can handle that type. (The vorbis plugin will be selected.) (see xmms_vorbis_plugin_setup() in src/plugins/vorbis/vorbisfile.c)
  7. The vorbis plugin's initialization results in a new stream type {'mime': 'audio/pcm-data', 'fmt': 'S16', 'rate': 44100, 'channels': 2} (see xmms_vorbis_init() in src/plugins/vorbis/vorbisfile.c)
  8. We notice that this format is in our goal_list, i.e. a format that the output supports. We are finished creating a transform chain!
  9. If, however, the format is not in the goal_list, the list of xform plugins will be traversed again for a plugin that can handle that type and the converter plugin will be found (its input type constraint is {'mime': 'audio/pcm-data'}). The converter will convert the audio format to one in the goal_list.

The finished chain of xforms will be used by the output to get the audiodata out:

  1. The output will read from the last plugin in the chain (i.e. vorbis) and will call its read method to get more data. (see xmms_output_filler() in src/xmms/output.c, xmms_xform_this_read() in src/xmms/xform.c and xmms_vorbis_read() in src/plugins/vorbis/vorbisfile.c)
  2. That read method might want to read more data from its underlying xform, and calls xmms_xform_read. (see vorbis_callback_read() in src/plugins/vorbis/vorbisfile.c and xmms_xform_read() in src/xmms/xform.c)
  3. That will invoke the read method on the magic plugin which just calls xmms_xform_read again, which invokes the read method on the file xform plugin. (see xmms_magic_plugin_read() in src/xmms/magic.c and xmms_file_read() in src/plugins/file/file.c)

Potential Uses

The magic file type detection is also implemented as a xform. The magic xform takes "application/octet-stream" as input and outputs the data type it detects. This sounds like it will introduce a bit of overhead at first, but keep in mind that there is no buffering involved, all magic resolving is set within the init function, and the read method of the magic transform is simply:

static gint
xmms_magic_plugin_read (xmms_xform_t *xf, void *b, gint len,
xmms_error_t *err)
{
	return xmms_xform_read (xf, b, len, err);
}

This makes everything unbuffered, which isn't really nice in all cases. However, buffers can be implemented as transform plugins and added to the chain as well! A buffer transform simply outputs the same type as it inputs, but implements a buffer in another thread. Such a plugin will probably be inserted at the end of the chain. But there are also practical cases where a buffer could be used elsewhere along the chain. Here are some examples illustrating both cases:

  • file <- magic <- ogg <- vorbis <- buffer
  • curl <- icy-meta <- buffer <- magic <- ogg <- vorbis <- buffer

Audio format converter is a transform too!

A future addition might be some kind of smart buffer that does some kind of measurement on the read rates and tries to decide if it should do buffering at all. But that is something that can be done at a (much) later stage.

Implementation Status

2006/02/06

This is how it works in Anders' local tree right now, so things might still change. Basic playback is working, but the code needs some cleanup before it can be comitted, and features such as seeking and mediainfo are not yet implemented. The plugins will probably look like this: http://exodus.xmms.se/~andersg/mad.c.txt http://exodus.xmms.se/~andersg/file.c.txt

2006-03-18

I just pushed a preview to my tree. Basic stuff should work. Only file, mad and modplug plugins are converted. Seeking doesn't work, playlistplugins doesn't work, id3v1-tags doesn't work. The xforms plugins uses a new plugin format, please comment on this!


2006-03-30

Everything reworked to new plugin api.

Plugin conversion guidelines

Use xform metadata api

Old code like:

xmms_medialib_session_t *session = xmms_medialib_begin_write ();
xmms_medialib_entry_t entry = xmms_xform_entry_get (xform);
xmms_medialib_entry_property_set_str (session, entry, XMMS_MEDIALIB_ENTRY_PROPERTY_ARTIST, mediainfo->artist);
xmms_medialib_end (session);
xmms_medialib_entry_send_update (entry);

should be changed to:

xmms_xform_metadata_set_str (xform, XMMS_MEDIALIB_ENTRY_PROPERTY_ARTIST, mediainfo->artist);