/* * 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