/*
* This file is part of libplacebo.
*
* libplacebo is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* libplacebo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with libplacebo. If not, see .
*/
#ifndef LIBPLACEBO_FRAME_QUEUE_H
#define LIBPLACEBO_FRAME_QUEUE_H
#include
#include
PL_API_BEGIN
// An abstraction layer for automatically turning a conceptual stream of
// (frame, pts) pairs, as emitted by a decoder or filter graph, into a
// `pl_frame_mix` suitable for `pl_render_image_mix`.
//
// This API ensures that minimal work is performed (e.g. only mapping frames
// that are actually required), while also satisfying the requirements
// of any configured frame mixer.
//
// Thread-safety: Safe
typedef struct pl_queue_t *pl_queue;
enum pl_queue_status {
PL_QUEUE_OK, // success
PL_QUEUE_EOF, // no more frames are available
PL_QUEUE_MORE, // more frames needed, but not (yet) available
PL_QUEUE_ERR = -1, // some unknown error occurred while retrieving frames
};
struct pl_source_frame {
// The frame's presentation timestamp, in seconds relative to the first
// frame. These must be monotonically increasing for subsequent frames.
// To implement a discontinuous jump, users must explicitly reset the
// frame queue with `pl_queue_reset` and restart from PTS 0.0.
double pts;
// The frame's duration. This is not needed in normal scenarios, as the
// FPS can be inferred from the `pts` values themselves. Providing it
// only helps initialize the value for initial frames, which can smooth
// out the interpolation weights. Its use is also highly recommended
// when displaying interlaced frames. (Optional)
float duration;
// If set to something other than PL_FIELD_NONE, this source frame is
// marked as interlaced. It will be split up into two separate frames
// internally, and exported to the resulting `pl_frame_mix` as a pair of
// fields, referencing the corresponding previous and next frames. The
// first field will have the same PTS as `pts`, and the second field will
// be inserted at the timestamp `pts + duration/2`.
//
// Note: As a result of FPS estimates being unreliable around streams with
// mixed FPS (or when mixing interlaced and progressive frames), it's
// highly recommended to always specify a valid `duration` for interlaced
// frames.
enum pl_field first_field;
// Abstract frame data itself. To allow mapping frames only when they're
// actually needed, frames use a lazy representation. The provided
// callbacks will be invoked to interface with it.
void *frame_data;
// This will be called to map the frame to the GPU, only if needed.
//
// `tex` is a pointer to an array of 4 texture objects (or NULL), which
// *may* serve as backing storage for the texture being mapped. These are
// intended to be recreated by `map`, e.g. using `pl_tex_recreate` or
// `pl_upload_plane` as appropriate. They will be managed internally by
// `pl_queue` and destroyed at some unspecified future point in time.
//
// Note: If `map` fails, it will not be retried, nor will `discard` be run.
// The user should clean up state in this case.
bool (*map)(pl_gpu gpu, pl_tex *tex, const struct pl_source_frame *src,
struct pl_frame *out_frame);
// If present, this will be called on frames that are done being used by
// `pl_queue`. This may be useful to e.g. unmap textures backed by external
// APIs such as hardware decoders. (Optional)
void (*unmap)(pl_gpu gpu, struct pl_frame *frame, const struct pl_source_frame *src);
// This function will be called for frames that are deemed unnecessary
// (e.g. never became visible) and should instead be cleanly freed.
// (Optional)
void (*discard)(const struct pl_source_frame *src);
};
// Create a new, empty frame queue.
//
// It's highly recommended to fully render a single frame with `pts == 0.0`,
// and flush the GPU pipeline with `pl_gpu_finish`, prior to starting the timed
// playback loop.
PL_API pl_queue pl_queue_create(pl_gpu gpu);
PL_API void pl_queue_destroy(pl_queue *queue);
// Explicitly clear the queue. This is essentially equivalent to destroying
// and recreating the queue, but preserves any internal memory allocations.
//
// Note: Calling `pl_queue_reset` may block, if another thread is currently
// blocked on a different `pl_queue_*` call.
PL_API void pl_queue_reset(pl_queue queue);
// Explicitly push a frame. This is an alternative way to feed the frame queue
// with incoming frames, the other method being the asynchronous callback
// specified as `pl_queue_params.get_frame`. Both methods may be used
// simultaneously, although providing `get_frame` is recommended since it
// avoids the risk of the queue underrunning.
//
// When no more frames are available, call this function with `frame == NULL`
// to indicate EOF and begin draining the frame queue.
PL_API void pl_queue_push(pl_queue queue, const struct pl_source_frame *frame);
// Variant of `pl_queue_push` that blocks while the queue is judged
// (internally) to be "too full". This is useful for asynchronous decoder loops
// in order to prevent the queue from exhausting available RAM if frames are
// decoded significantly faster than they're displayed.
//
// The given `timeout` parameter specifies how long to wait before giving up,
// in nanoseconds. Returns false if this timeout was reached.
PL_API bool pl_queue_push_block(pl_queue queue, uint64_t timeout,
const struct pl_source_frame *frame);
struct pl_queue_params {
// The PTS of the frame that will be rendered. This should be set to the
// timestamp (in seconds) of the next vsync, relative to the initial frame.
//
// These must be monotonically increasing. To implement a discontinuous
// jump, users must explicitly reset the frame queue with `pl_queue_reset`
// and restart from PTS 0.0.
double pts;
// The radius of the configured mixer. This should be set to the value
// as returned by `pl_frame_mix_radius`.
float radius;
// The estimated duration of a vsync, in seconds. This will only be used as
// a hint, the true value will be estimated by comparing `pts` timestamps
// between calls to `pl_queue_update`. (Optional)
float vsync_duration;
// If the difference between `pts` and the closest frame is smaller than
// this delta (in seconds), the mismatch will be assumed as drift/jitter
// and dynamically subtracted from all future pl_queue_update calls, until
// the queue is either reset or the PTS jumps by a large amount. (Optional)
float drift_compensation;
// If the difference between the (estimated) vsync duration and the
// (measured) frame duration is smaller than this threshold, silently
// disable interpolation and switch to ZOH semantics instead.
//
// For example, a value of 0.01 allows the FPS to differ by up to 1%
// without being interpolated. Note that this will result in a continuous
// phase drift unless also compensated for by the user, which will
// eventually resulted in a dropped or duplicated frame. (Though this can
// be preferable to seeing that same phase drift result in a temporally
// smeared image)
float interpolation_threshold;
// Specifies how long `pl_queue_update` will wait for frames to become
// available, in nanoseconds, before giving up and returning with
// QUEUE_MORE.
//
// If `get_frame` is provided, this value is ignored by `pl_queue` and
// should instead be interpreted by the provided callback.
uint64_t timeout;
// This callback will be used to pull new frames from the decoder. It may
// block if needed. The user is responsible for setting appropriate time
// limits and/or returning and interpreting QUEUE_MORE as sensible.
//
// Providing this callback is entirely optional. Users can instead choose
// to manually feed the frame queue with new frames using `pl_queue_push`.
enum pl_queue_status (*get_frame)(struct pl_source_frame *out_frame,
const struct pl_queue_params *params);
void *priv;
};
#define PL_QUEUE_DEFAULTS \
.drift_compensation = 1e-3f, \
.interpolation_threshold = 1e-6f,
#define pl_queue_params(...) (&(struct pl_queue_params) { PL_QUEUE_DEFAULTS __VA_ARGS__ })
// Advance the frame queue's internal state to the target timestamp. Any frames
// which are no longer needed (i.e. too far in the past) are automatically
// unmapped and evicted. Any future frames which are needed to fill the queue
// must either have been pushed in advance, or will be requested using the
// provided `get_frame` callback. If you call this on `out_mix == NULL`, the
// queue state will advance, but no frames will be mapped.
//
// This function may return with PL_QUEUE_MORE, in which case the user may wish
// to ensure more frames are available and then re-run this function with the
// same parameters. In this case, `out_mix` is still written to, but it may be
// incomplete (or even contain no frames at all). Additionally, when the source
// contains interlaced frames (see `pl_source_frame.first_field`), this
// function may return with PL_QUEUE_MORE if a frame is missing references to
// a future frame.
//
// The resulting mix of frames in `out_mix` will represent the neighbourhood of
// the target timestamp, and can be passed to `pl_render_image_mix` as-is.
//
// Note: `out_mix` will only remain valid until the next call to
// `pl_queue_update` or `pl_queue_reset`.
PL_API enum pl_queue_status pl_queue_update(pl_queue queue, struct pl_frame_mix *out_mix,
const struct pl_queue_params *params);
// Returns a pl_queue's internal estimates for FPS and VPS (vsyncs per second).
// Returns 0.0 if no estimate is available.
PL_API float pl_queue_estimate_fps(pl_queue queue);
PL_API float pl_queue_estimate_vps(pl_queue queue);
// Returns the number of frames currently contained in a pl_queue.
PL_API int pl_queue_num_frames(pl_queue queue);
// Returns the current PTS offset factor, as determined by the PTS drift
// compensation algorithm. This value is added onto all incoming values of
// pl_queue_params.pts.
PL_API double pl_queue_pts_offset(pl_queue queue);
// Inspect the contents of the Nth queued frame. Returns false if `idx` is
// out of range.
//
// Warning: No guarantee is made to ensure validity of `out->frame_data`
// after this call. In particular, pl_queue_* calls made from another thread
// may call `discard()` on the frame in question. The user bears responsibility
// to avoid accessing `out->frame_data` in a multi-threaded scenario unless
// an external guarantee can be made that the frame won't be dequeued until
// it is done being used by the user.
PL_API bool pl_queue_peek(pl_queue queue, int idx, struct pl_source_frame *out);
PL_API_END
#endif // LIBPLACEBO_FRAME_QUEUE_H