- Linux GCC rejects QMetaType instantiation over incomplete types (static_assert(sizeof(T))); Q_DECLARE_OPAQUE_POINTER every opaque OakEngine* handle in the public headers so QList<OakEngineTask*> etc compile - Windows: gtest discovery ran the fresh exe before the DLLs were next to it; use DISCOVERY_MODE PRE_TEST on WIN32 and copy the DLLs for ctest time
220 lines
7.8 KiB
C
220 lines
7.8 KiB
C
/***
|
|
|
|
Oak - Non-Linear Video Editor
|
|
Copyright (C) 2026 Oak Team
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program 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 General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
***/
|
|
|
|
#ifndef OAKENGINE_PLAYBACK_H
|
|
#define OAKENGINE_PLAYBACK_H
|
|
|
|
#include <stdint.h>
|
|
|
|
#include "export.h"
|
|
#include "timeline.h"
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
/**
|
|
* @file playback.h
|
|
* @brief C ABI for asynchronous headless playback of a sequence
|
|
*
|
|
* An OakEnginePlayback drives a pull thread that renders upcoming frames
|
|
* and 1/4-second audio blocks through the renderer family and delivers
|
|
* them through user callbacks -- the execution engine a viewer can be
|
|
* built on. The MVP deliberately covers forward playback at constant or
|
|
* sped-up rates only: no negative speed, no variable shuttle, no
|
|
* tape-style scrub audio.
|
|
*
|
|
* Threading: every callback fires ON THE PULL THREAD (one background
|
|
* thread per instance). Consumers that need the main thread (widgets,
|
|
* most GUI work) must marshal the data themselves. The payload pointers
|
|
* inside oak_playback_frame/oak_playback_audio are valid only until the
|
|
* callback returns (the engine owns and recycles the buffers); copy what
|
|
* you keep.
|
|
*
|
|
* Audio output: when an olive::AudioManager instance exists (the
|
|
* application creates it; bare facade processes usually have none), each
|
|
* audio block is also pushed to it (packed float32) and the playback
|
|
* position is read from its output clock (AudioManager::seconds(),
|
|
* latency-compensated). Without an instance, only the callbacks fire and
|
|
* the position falls back to the wall clock. Sequence position advances
|
|
* by consumed-output seconds * speed from the start timestamp either
|
|
* way.
|
|
*
|
|
* Event loop requirement: the process must pump a Qt event loop on its
|
|
* main thread while playing (the engine's conform/decode completions
|
|
* are posted there); GUI consumers get this for free, console consumers
|
|
* should drive QCoreApplication::processEvents() periodically (the same
|
|
* rule the synchronous facade waits follow).
|
|
*
|
|
* Frame pacing at speed != 1 mirrors the application's viewer: the
|
|
* timestamp step between delivered frames is llround(speed) (minimum
|
|
* 1), so > 1x skips frames while pacing stays clock-driven; audio is
|
|
* rendered per wall 1/4 second covering interval*speed of sequence time
|
|
* (no tempo/pitch correction at speed, an MVP limitation).
|
|
*
|
|
* Conventions match the other families: 0 (OAKENGINE_OK) / negative
|
|
* OAKENGINE_E_* codes, NULL handles as no-ops or OAKENGINE_E_INVALID,
|
|
* per-handle human-readable reason via oakengine_playback_last_error().
|
|
* All timestamps are frame numbers in the sequence's frame-rate
|
|
* timebase, like the rest of the timeline family.
|
|
*/
|
|
|
|
/**
|
|
* @brief Opaque playback engine handle. Free with
|
|
* oakengine_playback_free(); the sequence is borrowed (owned by its
|
|
* project).
|
|
*/
|
|
typedef struct OakEnginePlayback OakEnginePlayback;
|
|
|
|
/**
|
|
* @brief POD video frame delivered to the frame callback.
|
|
*
|
|
* `timestamp` is the frame number in the sequence's timebase; `format`
|
|
* is an olive::core::PixelFormat::Format value; `linesize` is the
|
|
* stride in bytes. `data` is owned by the engine and valid only until
|
|
* the callback returns.
|
|
*/
|
|
typedef struct oak_playback_frame {
|
|
int64_t timestamp;
|
|
int width;
|
|
int height;
|
|
int format;
|
|
int linesize;
|
|
const void *data;
|
|
} oak_playback_frame;
|
|
|
|
/**
|
|
* @brief POD audio block delivered to the audio callback.
|
|
*
|
|
* `start_ts` is the block's start in sequence timebase units,
|
|
* `sample_count` the frames per channel, `channel_data` planar float
|
|
* pointers (engine-owned, valid only until the callback returns).
|
|
*/
|
|
typedef struct oak_playback_audio {
|
|
int64_t start_ts;
|
|
int channels;
|
|
int sample_rate;
|
|
int64_t sample_count;
|
|
const float *const *channel_data;
|
|
} oak_playback_audio;
|
|
|
|
/**
|
|
* @brief Create a playback engine for `seq` producing `width`x`height`
|
|
* frames at `fps_num`/`fps_den`.
|
|
*
|
|
* Rendering goes through the renderer family, so actual playback
|
|
* requires the engine initialized with OAKENGINE_INIT_RENDER (starting
|
|
* without it fails with OAKENGINE_E_STATE). Returns NULL on invalid
|
|
* arguments (NULL sequence, non-positive size or frame rate).
|
|
*/
|
|
OAKENGINE_API OakEnginePlayback *oakengine_playback_create(
|
|
OakEngineSequence *seq, int width, int height, int fps_num,
|
|
int fps_den);
|
|
|
|
/**
|
|
* @brief Stop playback, join the pull thread and free the instance.
|
|
* NULL-safe. Must NOT be called from inside a frame/audio callback (the
|
|
* pull thread cannot join itself; use oakengine_playback_stop() there
|
|
* and free from another thread afterwards).
|
|
*/
|
|
OAKENGINE_API void oakengine_playback_free(OakEnginePlayback *self);
|
|
|
|
/**
|
|
* @brief Install the frame callback (NULL to clear). Fires on the pull
|
|
* thread; the payload is valid only during the call.
|
|
*/
|
|
OAKENGINE_API int oakengine_playback_set_frame_callback(
|
|
OakEnginePlayback *self,
|
|
void (*on_frame)(const oak_playback_frame *frame, void *userdata),
|
|
void *userdata);
|
|
|
|
/**
|
|
* @brief Install the audio callback (NULL to clear). Fires on the pull
|
|
* thread; the payload is valid only during the call.
|
|
*/
|
|
OAKENGINE_API int oakengine_playback_set_audio_callback(
|
|
OakEnginePlayback *self,
|
|
void (*on_audio)(const oak_playback_audio *audio, void *userdata),
|
|
void *userdata);
|
|
|
|
/**
|
|
* @brief Start (or re-base) playback at `start_ts` with `speed`.
|
|
*
|
|
* `speed` must be > 0 (OAKENGINE_E_INVALID otherwise; negative speed is
|
|
* outside the MVP). Starting while already playing re-anchors at
|
|
* `start_ts`. Requires OAKENGINE_INIT_RENDER (OAKENGINE_E_STATE).
|
|
*/
|
|
OAKENGINE_API int oakengine_playback_start(OakEnginePlayback *self,
|
|
int64_t start_ts, double speed);
|
|
|
|
/**
|
|
* @brief Pause playback (idempotent). The position freezes; resume by
|
|
* calling oakengine_playback_start() at the frozen (or any) timestamp.
|
|
*/
|
|
OAKENGINE_API int oakengine_playback_pause(OakEnginePlayback *self);
|
|
|
|
/**
|
|
* @brief Stop playback (idempotent): the pull thread exits and the
|
|
* position resets to the last start timestamp (0 before the first
|
|
* start). May be called from inside a callback (the pull thread then
|
|
* detaches and exits on its own; oakengine_playback_free() waits it
|
|
* out).
|
|
*/
|
|
OAKENGINE_API int oakengine_playback_stop(OakEnginePlayback *self);
|
|
|
|
/**
|
|
* @brief Current playback position as a frame timestamp.
|
|
*
|
|
* Read from the audio output clock when an AudioManager instance is
|
|
* pushing audio (the master clock), otherwise from the wall clock; a
|
|
* frozen value while paused, the last start timestamp while stopped.
|
|
*/
|
|
OAKENGINE_API int oakengine_playback_get_position(
|
|
const OakEnginePlayback *self, int64_t *ts);
|
|
|
|
/**
|
|
* @brief Change the speed mid-playback (`speed` > 0). Re-anchors at the
|
|
* current position so delivery stays monotonic. OAKENGINE_E_INVALID
|
|
* for speed <= 0.
|
|
*/
|
|
OAKENGINE_API int oakengine_playback_set_speed(OakEnginePlayback *self,
|
|
double speed);
|
|
|
|
/**
|
|
* @brief 1 while playing (0 when paused, stopped, or after the
|
|
* end-of-stream auto-stop). 0 on a NULL handle.
|
|
*/
|
|
OAKENGINE_API int oakengine_playback_is_playing(
|
|
const OakEnginePlayback *self);
|
|
|
|
/**
|
|
* @brief Human-readable reason of the last failed call on this handle
|
|
* (buf/size convention). Empty when the last call succeeded.
|
|
*/
|
|
OAKENGINE_API int oakengine_playback_last_error(
|
|
const OakEnginePlayback *self, char *buf, int buf_size);
|
|
|
|
#ifdef __cplusplus
|
|
}
|
|
Q_DECLARE_OPAQUE_POINTER(OakEnginePlayback *)
|
|
#endif
|
|
|
|
#endif /* OAKENGINE_PLAYBACK_H */
|