PM123 Output Plugins

Output plugins must implement and export the functions defined in output_plug.h. There are different interface versions. It is recommended to use level 3.

Interface revision level 3 (recommended)

Initialization interface

The initialization interface does not need to be thread safe.

ULONG DLLENTRY output_init  (struct OUTPUT_STRUCT** a)
ULONG DLLENTRY output_uninit(struct OUTPUT_STRUCT* a)

The output_init function is called when the user requests the use of your output plugin. So only one output plugin is active at any given time. It should initialize the control variables for an eventual call to output_command with OUTPUT_OPEN.
output_uninit is called when another output plugin is request by the user and should free the allocated memory for a.

Control interface

The control interface does not need to be thread safe itself, but it is called from a different thread than the data interface.

ULONG DLLENTRY output_command(struct OUTPUT_STRUCT* a, ULONG msg, OUTPUT_PARAMS2* info)
The status graph is as follows:
  1. OUTPUT_SETUP
  2. OUTPUT_OPEN - now we are active. The data interface may be used.
    Optionally repeat this step if another song is to be played.
  3. OUTPUT_CLOSE - now we are no longer active.

The other function codes (OUTPUT_VOLUME, OUTPUT_PAUSE and OUTPUT_TRASH_BUFFERS) are allowed only in active mode.

OUTPUT_SETUP - setup the event callback

Only the fields OutEvent and W should be read during this call.

OUTPUT_OPEN - opens the device or file needed for output

This call tells the device about the object that is about to be played (fields URL and Info). The Output could use this information to get the format (sampling rate and channels) that is likely to come. The field PlayingPos contains the time index of the first sample of the decoded object.

The URL parameter at the OUTPUT_OPEN call uses the following syntax:
file:///D:/Music/my favorite song.mp3
file://server/share/favorite song.mp3 (UNC path)
http://... (as you would expect)
cdda:///H:/track02

The URI may change during playback, if the playback moves to the next file in playlist mode. This is signaled with a new OUTPUT_OPEN call while the device is already open. The call is always before the first decoded samples of the mentioned object arrive. The new object's data start with the time index PlayingPos. Note that multiple short items might be in the processing queue before their samples arrive. But usually only one song is decoded in advance.
As long as you do not care about the new object you could ignore this message. But note that the format might change with a new item.

OUTPUT_TRASH_BUFFERS - trash any buffers currently awaiting to be played

This command tells the output to discard any buffers not yet played. It is used to make seek operations responsive. This implies also to discard an outstanding flush request and any information from OUTPUT_OPEN calls, except for the last one.

This message should immediately release a thread waiting at output_request_buffer for data to be played. This is essential to stop playback while in paused mode.

If the target is a sound device, the output plugin should usually immediately respond with an OUTEVENT_LOW_WATER event, because there are no more buffers to play at the moment.

OUTPUT_VOLUME - changes the volume of an output device

On this call the Volume field contains the new volume setting. The change should apply as soon as possible.

The values are in the range [0,1]. The scale should behave like a volume control of a hi-fi unit, i.e. it should not use a linear scale. If your hardware or API has a linear scale, you could use the approximation linscale = value / (1 + sqrt(10) * (1 - value)).

PM123 since 1.41 will no longer call this function before OUTPUT_OPEN.

OUTPUT_PAUSE - pauses or resume the playback

The command should immediately pause or resume playback, depending on the Pause field in the control structure. If playback is paused the plugin should sooner or later block in output_request_buffer if the buffers become filled.

OUTPUT_CLOSE - immediately stop playback and close the audio device

There are two cases, when OUTPUT_CLOSE can arrive.

  1. The output has sent OUTEVENT_END_OF_DATA.
    In this case no more samples are passed to the output and the device could safely be closed.
  2. The user aborts playback.
    Any thread blocked at output_request_buffer or output_commit_buffermust be released immediately. This is essential to stop playback while in paused mode. output_request_buffer should return ≤ 0 in this case.
PM123 will not call output_uninit unless the decoder thread really returned.
Events
  1. The output plugin must call the event handler output_event with the parameter OUTEVENT_PLAY_ERROR when a playback error occurs. This will stop the playback, but it is not guaranteed that this is done immediately.
  2. The output plugin must call output_event with OUTEVENT_END_OF_DATA when the last sample is really processed. This must not be done unless the function output_play_samples has been called with a buffer pointer of NULL (flush signal).
  3. The output plugin may raise the events OUTEVENT_LOW_WATER and OUTEVENT_HIGH_WATER to signal that the buffers are running low or the buffers are getting full respectively. This is used by PM123 to control the scheduling priority.
  4. The event handler may be called from any thread in any context.

Data interface

The data interface is only used when the control interface is in activated state.

The data interface does not need to be thread safe itself, but it is called from a different thread than the control interface.

The level 3 interface allocates the sample buffers by the plugin rather than the data source. This is known to cause less double buffering. The buffer size is no longer fixed. The functions output_request_buffer and output_commit_buffer have to be called alternately. Anything else is an error.

All samples are passed as 32 floating point regardless what type the input has been.

int DLLENTRY output_request_buffer(struct OUTPUT_STRUCT* a, FORMAT_INFO2* format, float** buf)

This function is called by the decoder or last in chain filter plugin to store samples for playing. The function call may block until enough buffer space is available.

After a flush request (buf == NULL) there is no need to call output_commit_buffer. The flush signal should be used to play any internal buffers regardless if they are completely full or not.
Note that the flush signal is an implicit request for an OUTEVENT_END_OF_DATA event. The output_request_buffer call shall not block until all buffers are played.
Do not close the output if you receive a flush request. The user could use navigation commands to resume playback while the flush request already is in the processing queue.

void DLLENTRY output_commit_buffer(struct OUTPUT_STRUCT* a, int len, PM123_TIME pos)

This functions are called by the decoder or last in chain filter plugin to play the stored samples.

Status interface

The status interface must be thread safe and re-entrant.

PM123_TIME DLLENTRY output_playing_pos(struct OUTPUT_STRUCT* a)
This function returns the pos from the buffer that the user currently hears. The return value is a time index in seconds. The plugin may use its knowledge to calculate the time with higher resolution than one buffer. But you must not make any assumptions about the zero point.
BOOL DLLENTRY output_playing_data(void* a)
Returns TRUE if the output plugin still has some buffers to play.
ULONG DLLENTRY output_playing_samples(struct OUTPUT_STRUCT* a, PM123_TIME offset, OUTPUT_PLAYING_BUFFER_CB cb, void* param)
This function is used by visual plugins so the user can visualize what is currently being played. It requests the currently playing samples asynchronously. The callback may be invoked after output_playing_samples returned without an error from a different thread, at a different priority and from a synchronized context. Each successful call to output_playing_samples creates exactly one callback with done = TRUE.
typedef void (DLLENTRY* OUTPUT_PLAYING_BUFFER_CB)(void* param, const FORMAT_INFO2* format, const float* samples, int count,
PM123_TIME pos, BOOL* done);

This callback receives chunks of sample data that is currently played by the output plugin.

Interface revision level 0 and 1 (deprecated)

This interface revisions should not be used for development because the implementations tends to modify the current thread's priority to boost the speed of the data source. This implies that the current thread while calling output_play_samples is the bottleneck and that it is always the same thread. Both is not true in general (e.g. remote data sources).

Initialization interface

See level 3 interface. This part of the interface has not been changed.

Control interface

The control interface does not need to be thread safe itself, but it is called from a different thread than the data interface.

ULONG DLLENTRY output_command(struct OUTPUT_STRUCT *a, ULONG msg, OUTPUT_PARAMS *info)

There is a lot of commands to implement for this function. Parameters needed for each of them are described in the definition of the structure in the .h file.

The status graph is as follows:

  1. OUTPUT_VOLUME
  2. OUTPUT_SETUP
  3. OUTPUT_OPEN - now we are active. The data interface may be used.
  4. OUTPUT_OPEN - next song
    ...
  5. OUTPUT_CLOSE - now we are no longer active.

The other function codes (OUTPUT_PAUSE and OUTPUT_TRASH_BUFFERS) are allowed only in active mode. OUTPUT_VOLUME is always allowed.

The output plugin must WinPostMsg() the following messages to hwnd:

Data interface

The data interface is only used when the control interface is in activated state.

The data interface does not need to be thread safe itself, but it is called from a different thread than the control interface.

int DLLENTRY output_play_samples(struct OUTPUT_STRUCT* a, FORMAT_INFO* format, char* buf, int len, int posmarker)

This function is called by the decoder or last in chain filter plugin to play samples.

Status interface

The status interface must be thread safe and reentrant.

int DLLENTRY output_playing_pos(struct OUTPUT_STRUCT* a)
This function returns the posmarker from the buffer that the user currently hears. The return value is a time index in milliseconds. The plugin may use this knowledge to calculate time indices with higher resolution than one buffer. But you must not make any assumptions about the zero point.
BOOL DLLENTRY output_playing_data(struct OUTPUT_STRUCT* a)
Returns TRUE if the output plugin still has some buffers to play.
ULONG DLLENTRY output_playing_samples(struct OUTPUT_STRUCT* a, FORMAT_INFO* info, char* buf, int len)

This function is used by visual plugins so the user can visualize what is currently being played. len is usually in the order of 2048 samples or less. So check that amount usually required by your visual plugins before making complicated buffering functions in your output plugin.