PM123 Decoder Plugins

Decoder plugins must implement and export the functions defined in decoder_plug.h.

There are two interface revision levels. For developing new plugins the level 3 interface is strongly recommended. But level 0/1 plugins are still supported. The level 2 of PM123 1.40 is discontinued.

Decoder plugins are used to decode audio data as well as to examine playlists. Each item is identified by an URI.
You should not take the term 'playlist' too literally. It only means that an URI has logically a sequence of sub URIs, i.e. the URI has to be enumerable. This applies to a playlist as well as to a file system folder or a compact disc containing tracks.
In theory an item could be enumerable as well as directly playable. E.g. index tracks of a CD or a CUE sheet. But this has not been well tested in the PM123 core so far.

Interface revision level 3 (recommended)

The level 3 interface has the following components:

  1. Playback interface, status interface and output interface
    This interfaces are used to play audio data. Plugins that only support playlists do not need to export these interfaces.
  2. Info interface, save interface
    This interface is used to obtain or change information of a song or a playlist and to identify whether an item is supported by the plugin.
  3. GUI enhancement interface

Info interface

The info interface functions must be independent of the current decoder status. They should always be functional and give consistent results in any conditions. The functions must be thread safe.

decoder_support

ULONG DLLENTRY decoder_support(const DECODER_FILETYPE** types, int* count)

This is used by PM123 to suggest to the user what he can play with the decoder. Furthermore, by default PM123 will not query the decoder about files or other objects that are not listed in the above filter list to keep performance up.

The function is called again after a successful call to plugin_configure. This allows to modify the supported file types through the configuration.

decoder_fileinfo

ULONG DLLENTRY decoder_fileinfo(const char *url, XFILE* handle, int* what, const INFO_BUNDLE* info,
  DECODER_INFO_ENUMERATION_CB cb, void* param)
bit in *what on input bit in *what on return corresponding info available corresponding fields in INFO_BUNDLE
reset reset don't care ignored
set reset don't care not allowed!
don't care set no leave unmodified
don't care set yes fill with valid content

If a decoder knows that some information, that is not explicitly requested, is not available for the current URI or it knows it's content during processing of the request, it should always set the corresponding bit in *what and return the information if applicable. This avoids redundant calls to decoder_fileinfo.

PM123 does not need all informations for all kind of objects. The aggregate type recursive playlist information is never requested explicitly by PM123. PM123 will also never request INFO_PHYS if a handle is not NULL. However, a plugin may supply aggregate information if it is available. E.g. playlists may store cached information about their children to improve performance. If you are in doubt, do not supply this kind of information.

Legend:
X = required if requested by the PM123
Q = may be requested by the PM123
C = my be cached and returned by the decoder
O = may be overridden by a playlist item

info type song playlist playlist item
phys X X C
tech X X C
obj X X C
meta X X C O
attr Q X C O
children Q X
rpl   C C
drpl   C C
item

O

DECODER_INFO_ENUMERATION_CB
void (DLLENTRY* cb)(void* param, const char* url, const INFO_BUNDLE* info, int cached, int reliable)

Playback interface

decoder_init, decoder_uninit

int DLLENTRY decoder_init (struct DECODER_STRUCT **w)
The decoder_init function is called when PM123 needs the specified decoder to play the stream demanded by the user. So only one decoder plugin is active at any given time. It should initialize the necessary semaphores and threads.
BOOL DLLENTRY decoder_uninit(struct DECODER_STRUCT *w)

decoder_uninit is called when another decoder than yours is needed, and should destroy the decoder's thread, semaphores, other opened handles and free allocated memory for w. The decoder must not execute any callback function like OutRequestBuffer from another thread when decoder_uninit has returned.

decoder_command

ULONG DLLENTRY decoder_command(struct DECODER_STRUCT *w, ULONG msg, DECODER_PARAMS2 *params)

There are a lot of commands to implement for this function. Parameters needed for each of them are passed in the DECODER_PARAMS2 structure and described in detail here and in the decoder_plug.h include. The decoder necessarily should support following commands: DECODER_SETUP, DECODER_PLAY and DECODER_STOP.

In the level 3 interface the data source always passed as an URL. The URL parameter uses the following syntax:

file:///D:/Music/my favorite song.mp3
file://server/share/path/song.mp3 (UNC path)
http://... (as you would expect)
cdda:///H:/track02 (CD track)
cdda:///H:/ (CD TOC)
DECODER_SETUP
The DECODER_SETUP call is intended to capture the callback entries for the output interface. They do not change during decoding.
DECODER_PLAY

This command tell the decoder which song to decode. This should spawn a new thread that decodes the song and feeds the result to the output interface.

DECODER_JUMPTO

Tells the decoder to continue decoding at a certain location of the song. The structure member JumpTo is the location in seconds from the song's start.

DECODER_FFWD

Change the fast forward or rewind mode. The structure member SkipSpeed tell the decoder which playback speed is intended. The value i a delta to the normal playback speed, i.e. speed = SkipSpeed + 1. Examples:

There is no need to hit this value exactly. It is better to skip parts of the song instead of transforming the sample rate. Common values are marked bold. If a decoder can't support different speeds, it could simply use SkipSpeed > 0 for fast forward, and SkipSpeed < 0 for fest rewind.

Implementation hint: If you implement fast scan mode by seeking every 100 ms forward or reverse, then you need to seek SkipSpeed * 100 ms every time to get the desired average speed.

DECODER_STOP

Stop decoding of the current song. This should terminate the decoder thread. After DECODER_STOP the decoder should ignore all errors and simply terminate.

Status interface

The status interface has to be thread safe.

decoder_status

ULONG DLLENTRY decoder_status(struct DECODER_STRUCT *w)

decoder_length

PM123_TIME DLLENTRY decoder_length(struct DECODER_STRUCT *w)

The call to this function must be valid even if DECODER_STARTING or DECODER_STOPPED is reported (when the stream plays too fast for example). The function is used to follow increasing length of files that are written on the fly while playing.

Output interface

The decoder must use this interface to pass the decoded samples to the output stage. The samples must be passed as 32 bit floating point values.

Strictly speaking this is part of the playback interface, but the interface functions have to be called by the decoder in a separate thread. The function entry points for these callbacks are passed in DECODER_PARAMS2 at the DECODER_SETUP call.

The level 3 interface allocates the buffers by the consumer. This causes less double buffering and allows dynamic buffer sizes. In the optimal case the samples can be placed immediately in the output buffers of the audio device.

You must call OutRequestBuffer and OutCommitBuffer alternately to pass the samples to the next plugin. Anything else is an error. Note that any of the two functions might block.

OutRequestBuffer

int (DLLENTRYP OutRequestBuffer)(void* a, const FORMAT_INFO2* format, float** buf)

If you get a smaller buffer as you need to pass your data you should call OutRequestBuffer and OutCommitBuffer again until all your data is consumed. There is no guaranteed minimum size of the buffer, but you should not expect to get very small buffers quite often.

OutCommitBuffer

void (DLLENTRY* OutCommitBuffer)(void* a, int len, PM123_TIME posmarker)

The length must not be higher than the the return value from the previous OutRequestBuffer call. But it might be less than the requested length. This causes no significant performance impact as long as you do not always pass very few samples.

DecEvent

Send decoder event to the PM123 core.
void (DLLENTRY* DecEvent)(void* a, DECEVENTTYPE event, void* param)

The decoder plugin MUST call the above function on the following conditions:

Once a decoder raised a DECODER_PLAYSTOP or DECODER_ERROR event it shall not use any callback functions until it receives a DECODER_PLAY command.

decoder_event

Receive event from the PM123 core.
void DLLENTRY decoder_event(void* w, OUTEVENTTYPE event)

The events may be used to speed up the data source. If you get OUTEVENT_HIGH_WATER you should return to normal behavior. There is one task that the core engine does for you: PM123 automatically changes the priority of the decoder thread. So if there is nothing more the decoder can do (e.g. QoS settings) it could safely ignore the events.

The event handler may be called from any thread in any context. It might be called when the decoder is in stopped state if the end of the stream has been reached recently. This should be ignored. You will usually get a OUTEVENT_LOW_WATER call immediately after the decoding started or after a seek command because the buffers are not yet filled.

The export of decoder_event is optional.

Save interface

decoder_saveinfo

ULONG DLLENTRY decoder_saveinfo(char* url, const META_INFO* info, int haveinfo, xstring* errortext)

The function modifies the meta information of a certain song. Calling the function should succeed even if the file is currently playing.

decoder_savefile

ULONG DLLENTRY decoder_savefile(const char* url, const char* format, int* what, const INFO_BUNDLE* info,
DECODER_SAVE_ENUMERATION_CB cb, void* param, xstring* errortext)

The function is used to modify a file (or other object) in place. It is currently only used to save playlists.

DECODER_SAVE_ENUMERATION_CB
int (DLLENTRY* cb)(void* param, xstring* url, const INFO_BUNDLE** info, int* cached, int* reliable)
This function retrieves the next sub item from the currently saved playlist. PM123 will fill all fields according to the above rules.
DECODER_FILETYPE
typedef struct
{ const char* category;
const char* eatype;
const char* extension;
int flags;
} DECODER_FILETYPE;
Field Meaning
category File type category, e.g. "Playlist file"
This field is only used for reading files.
eatype File type, e.g. "PM123 playlist file"
extension File extension, e.g. "*.lst;*.m3u"
flags Bit vector of DECODER_TYPE.
DECODER_FILENAME (1) - Decoder can play files of this type, should always be set for EA types.
DECODER_URL (2) - Decoder can play URIs (http, ftp, etc.), should be set for MIME types.
DECODER_SONG (0x0100) - Decoder can play songs with this file type.
DECODER_PLAYLIST (0x0200) - Decoder can play playlists with this file type.
DECODER_WRITABLE (0x1000) - Decoder can save items of this type.
DECODER_METAINFO (0x2000) - Decoder can save a meta info.

TODO

GUI enhancement interface

The following functions are used to improve the GUI of PM123 with plugin specific content.
All functions in this section are optional.

decoder_editmeta

ULONG DLLENTRY decoder_editmeta(HWND owner, const char* url)

If decoder_editmeta is not implemented, the edit tag entry is always disabled.

decoder_getwizard

const DECODER_WIZARD* DLLENTRY decoder_getwizard(void)

If the function is not implemented or returns NULL the context menu is not extended with entries specific to this plugin.

The field prompt is the menu text. It should not contain information about the accelerator key, because this is generated automatically.

The fields accel_key[2] and accel_opt[2] can be used to extend the accelerator table of PM123. They correspond to the fields key and fs of the ACCEL structure. The first set of entries is used for the PM123 main window and the playlist windows. The second set is for the Playlist Manager to append to the currently selected playlist.
You should set accel_key to 0 if you do not want an accelerator key for your plugin's wizard dialog. Be careful with the choice of the accelerator keys because there may be clashes. Using Alt for the first entry and Shift-Alt for the second entry as meta keys is recommended.

When the menu entry is selected the corresponding wizard function is called by PM123.

ULONG (DLLENTRY* wizard)(HWND owner, const char* title,
DECODER_INFO_ENUMERATION_CB callback, void* param)