engine: add the preview family to the C ABI facade

- clip-level loop mode (off/loop/clamp), undoable
- per-channel audio levels as one-frame RMS at a timestamp
- waveform min/max buckets over a footage range, rendered on demand
- two real fixes uncovered by this family: conform completion signals
  were starved by msleep-only waits (audio renders always came back
  empty), and incomplete tickets from conform-pending renders are now
  retried until the conform is ready
This commit is contained in:
2026-07-20 09:28:47 +08:00
parent 882ce8268c
commit bb17c59c0f
6 changed files with 810 additions and 16 deletions
+125
View File
@@ -0,0 +1,125 @@
/***
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_PREVIEW_H
#define OAKENGINE_PREVIEW_H
#include <stdint.h>
#include "export.h"
#include "footage.h"
#include "init.h"
#include "timeline.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @file preview.h
* @brief C ABI for preview state and readouts (loop mode, audio levels,
* waveform summary)
*
* This family is deliberately NOT realtime playback: the application's
* realtime transport is deeply coupled to its viewer widgets (viewer
* playback timer, audio queue, PortAudio manager). What a headless
* consumer needs is renderable through the renderer family; this family
* adds the remaining preview state and readouts.
*
* Loop mode note: the engine has no sequence-level loop state. LoopMode
* (engine/render/loopmode.h: off / loop / clamp) is a property of a CLIP
* (ClipBlock::k_loop_mode_input), which is what the application edits.
* The loop functions here therefore operate on clip handles, persisted
* through the node-graph and undoable like other parameter writes.
*
* Audio readouts are computed by rendering the exact range through
* RenderManager::render_audio() and reducing the samples in-process --
* the exact semantics are documented per function. Rendering audio may
* conform sources on first use; the wait is driven with an event loop
* like the export family (up to 120 s). Everything here works without
* GL. Errors follow the family model (oakengine_preview_last_error()).
*/
/** @brief Loop modes, mirroring olive::LoopMode. */
#define OAKENGINE_LOOP_MODE_OFF 0 /**< Play once (olive k_loop_mode_off). */
#define OAKENGINE_LOOP_MODE_LOOP 1 /**< Repeat the clip (olive k_loop_mode_loop). */
#define OAKENGINE_LOOP_MODE_CLAMP 2 /**< Hold first/last frame (olive k_loop_mode_clamp). */
/**
* @brief Human-readable reason for the last failed preview call on this
* thread (buf/size convention).
*/
OAKENGINE_API int oakengine_preview_last_error(char *buf, int buf_size);
/**
* @brief The clip's loop mode (OAKENGINE_LOOP_MODE_*;
* ClipBlock::loop_mode()). Returns the mode or OAKENGINE_E_INVALID.
*/
OAKENGINE_API int oakengine_clip_get_loop_mode(const OakEngineClip *self);
/**
* @brief Set the clip's loop mode (undoable parameter write, same command
* path as the node family). OAKENGINE_E_INVALID for an unknown mode.
*/
OAKENGINE_API int oakengine_clip_set_loop_mode(OakEngineClip *self,
int mode);
/**
* @brief Per-channel audio level of a sequence at `time_ts`.
*
* Exact semantics: the sequence's audio is rendered over ONE frame
* starting at `time_ts` (a frame timestamp in the sequence's frame-rate
* timebase, like the rest of the family) and the linear RMS
* (root-mean-square) of every channel is written to `values`, in the
* [0, 1] range for normalized audio. Ranges with no audio content yield
* exact zeros (the engine returns no allocated samples there).
* `channel_count` is the capacity of `values`; up to that many of the
* sequence's channels are written, the return value is the number of
* channels written, and a negative OAKENGINE_E_* code signals an error
* (e.g. no RENDER-less engine state issue -- audio renders without GL,
* but the engine must be initialized with OAKENGINE_INIT_RENDER because
* rendering goes through RenderManager).
*/
OAKENGINE_API int oakengine_preview_get_audio_levels(
OakEngineSequence *seq, int64_t time_ts, double *values,
int channel_count);
/**
* @brief Waveform min/max summary of a footage's audio over a range.
*
* The footage's audio is rendered from `start_ts` to `end_ts` (frame
* timestamps in the timebase of the project's first sequence's frame
* rate, same convention as the keyframe family) and each of the `count`
* equal-sized buckets covering the range yields the minimum and maximum
* sample value of `channel` in `min_vals`/`max_vals` (linear, unclamped
* source samples; silent or content-free buckets are exact zeros). The
* footage handle must be a borrowed import handle (probe handles are
* rejected with OAKENGINE_E_INVALID); `channel` must be within the
* footage's channel count and `count` must be > 0.
*/
OAKENGINE_API int oakengine_preview_get_waveform_summary(
OakEngineFootage *footage, int channel, int64_t start_ts,
int64_t end_ts, double *min_vals, double *max_vals, int count);
#ifdef __cplusplus
}
#endif
#endif /* OAKENGINE_PREVIEW_H */