refactor(codec): de-Qt oakcodec and wrap it in a pure C ABI; switch common handles to refcounted value structs

- oakcodec: de-Qt all 20 sources (QThread decode loop -> std::thread,
  QObject/signals -> callbacks), pure C ABI in include/codec with
  refcounted neutral handles (OakFrame/OakDecoder/OakEncoder),
  framemanager moved in from render, frame_to_buffer/buffer_to_frame
  moved in from oakcommon oiioutils, codec->task via submit callback
  (M8 will register), all cross-module calls go through the other
  side's C API, -fvisibility=hidden + OAKCODEC_API
- oakcommon: handles become refcounted value structs
  {ctx, addref, release, abi_version} (FFmpeg-style), pass-by-value
  signatures, free() as release wrapper; init_from_native/get_native
  for copyable value objects; OakCommonXxx renamed to OakXxx
- oakcommon: add logging (log_debug/info/warning/critical with level
  filtering and sink injection) + printf-style oakcommon_log C wrapper
- oakrender: add CancelAtom C API family; complete
  oakrender_color_processor_convert_frame; fix get_processor() missing
  definition and OCIO env var lookup
- tests: oakcommon 174, oaknode 96, oakrender 42, oakcodec 18, all
  green in their standalone builds
This commit is contained in:
2026-08-06 18:50:07 +08:00
parent edbd3913af
commit 3d004c081b
127 changed files with 13760 additions and 1222 deletions
+106
View File
@@ -0,0 +1,106 @@
/***
Oak Video Editor - 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 OAK_EDITOR_CODEC_CONFORM_H
#define OAK_EDITOR_CODEC_CONFORM_H
#include <stdint.h>
#include "error.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @file conform.h
* @brief C ABI for the oakcodec audio conform manager
* (olive::ConformManager): pcm waveform cache files used for fast
* audio scrubbing.
*
* Interim state (pre-M8): actual conform work is delegated to the global
* task submit callback (see task.h). While no callback is registered,
* state queries report OAKCODEC_CONFORM_UNAVAILABLE.
*/
#define OAKCODEC_CONFORM_EXISTS 0
#define OAKCODEC_CONFORM_GENERATING 1
#define OAKCODEC_CONFORM_UNAVAILABLE 2
/**
* @brief Create the ConformManager singleton (no-op when it exists).
*/
OAKCODEC_API int oakcodec_conform_create_instance(void);
/**
* @brief Destroy the ConformManager singleton (no-op when absent).
*/
OAKCODEC_API int oakcodec_conform_destroy_instance(void);
/**
* @brief Query the conform state of one audio stream, starting the
* conform when needed and possible.
*
* Addresses the source by filename/stream_index and the target audio
* format by sample_rate/channel_layout/sample_format
* (olive::core::SampleFormat::Format as int).
*
* When the conform files do not exist and a task submit callback is
* registered (task.h), the conform is submitted synchronously and the
* filesystem is re-checked; `wait` only controls whether a post-submit
* miss is reported as OAKCODEC_CONFORM_UNAVAILABLE (wait != 0) or
* OAKCODEC_CONFORM_GENERATING (wait == 0). Without a registrar the
* result is always OAKCODEC_CONFORM_UNAVAILABLE.
*
* @return One of OAKCODEC_CONFORM_* (non-negative), or a negative
* OAKCODEC_E_* code for invalid arguments.
*/
OAKCODEC_API int oakcodec_conform_get_state(const char *cache_path,
const char *source_filename, int stream_index,
int sample_rate, uint64_t channel_layout,
int sample_format, int wait);
/**
* @brief Number of conform (pcm) files for the given stream/params — one
* per channel; 0 on invalid arguments.
*/
OAKCODEC_API int oakcodec_conform_filename_count(const char *cache_path,
const char *source_filename, int stream_index,
int sample_rate, uint64_t channel_layout,
int sample_format);
/**
* @brief The `index`-th conform filename (buf/size getter).
*
* @return Required buffer size including NUL (non-negative), or a
* negative OAKCODEC_E_* code (OAKCODEC_E_NOT_FOUND when index is
* out of range).
*/
OAKCODEC_API int oakcodec_conform_filename_at(const char *cache_path,
const char *source_filename,
int stream_index, int sample_rate,
uint64_t channel_layout, int sample_format,
int index, char *buf, int buf_size);
#ifdef __cplusplus
}
#endif
#endif //OAK_EDITOR_CODEC_CONFORM_H
+212
View File
@@ -0,0 +1,212 @@
/***
Oak Video Editor - 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 OAK_EDITOR_CODEC_DECODER_H
#define OAK_EDITOR_CODEC_DECODER_H
#include <stdint.h>
#include "error.h"
#include "frame.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @file decoder.h
* @brief C ABI for oakcodec media decoders (olive::Decoder and its
* FFmpeg/OIIO implementations): probing, stream enumeration and
* CPU-frame decoding.
*
* Handles follow the neutral by-value convention documented in frame.h
* (and oakcommon's common/handle.h). Two usage patterns share the
* OakDecoder handle:
*
* - Probe: oakcodec_decoder_probe() inspects a file WITHOUT opening a
* decode session; the stream getters describe what was found.
* - Decode: oakcodec_decoder_init() + oakcodec_decoder_open() attach a
* decoder instance to one (filename, stream) pair; the decode
* functions then produce frames/audio.
*/
typedef struct OakDecoder {
void *ctx; /**< Opaque pointer to the reference-counted object. */
void (*addref)(void *ctx); /**< Atomically increments the count. */
void (*release)(void *ctx); /**< Decrements the count, destroys at 0. */
uint32_t abi_version; /**< OAKCODEC_ABI_VERSION. */
} OakDecoder;
/**
* @brief POD description of one probed video stream.
*
* duration_ts counts units of the stream's time base;
* time_base_num/den is seconds per time-base unit. color_primaries and
* color_trc carry the ISO/IEC 23001-8 code points the decoder reports
* (0 = unknown). interlaced is 1 when the stream is interlaced.
* format is an OakPixelFormat value (the decoder's native delivery
* format), channel_count its plane channel count.
*/
typedef struct oakcodec_video_stream_info {
int stream_index;
int width;
int height;
int frame_rate_num;
int frame_rate_den;
int64_t duration_ts;
int time_base_num;
int time_base_den;
int format;
int channel_count;
int color_primaries;
int color_trc;
int interlaced;
} oakcodec_video_stream_info;
/**
* @brief POD description of one probed audio stream.
*
* channel_layout is the ffmpeg-style channel mask (e.g. 0x3 = stereo).
*/
typedef struct oakcodec_audio_stream_info {
int stream_index;
int sample_rate;
uint64_t channel_layout;
int channel_count;
int64_t duration_ts;
int time_base_num;
int time_base_den;
} oakcodec_audio_stream_info;
/* ---- Probe (stateless inspection) ---------------------------------------- */
/**
* @brief Probe a media file: decoder name plus stream inventory.
*
* Tries each available decoder implementation (FFmpeg, then OIIO) and
* wraps the first one that recognizes the file. The returned handle only
* carries probe results; it cannot decode (use init + open for that).
*
* @return Handle with reference count 1, or an empty handle (ctx == NULL)
* when no decoder recognizes the file (oakcodec_probe_last_error()
* carries the reason).
*/
OAKCODEC_API OakDecoder oakcodec_decoder_probe(const char *filename);
/**
* @brief Thread-local error detail of the last failed probe on this
* thread (buf/size string getter convention).
*/
OAKCODEC_API int oakcodec_probe_last_error(char *buf, int buf_size);
/** @brief Probed decoder id ("ffmpeg"/"oiio", buf/size getter). */
OAKCODEC_API int oakcodec_decoder_probe_decoder_name(OakDecoder probe, char *buf,
int buf_size);
OAKCODEC_API int oakcodec_decoder_probe_video_stream_count(OakDecoder probe);
OAKCODEC_API int oakcodec_decoder_probe_audio_stream_count(OakDecoder probe);
OAKCODEC_API int oakcodec_decoder_probe_subtitle_stream_count(OakDecoder probe);
/**
* @brief Fill `out` with the video stream at `index` (0-based within the
* video stream list).
*
* @return OAKCODEC_OK, OAKCODEC_E_INVALID, or OAKCODEC_E_NOT_FOUND when
* index is out of range.
*/
OAKCODEC_API int oakcodec_decoder_probe_get_video_stream(OakDecoder probe, int index,
oakcodec_video_stream_info *out);
OAKCODEC_API int oakcodec_decoder_probe_get_audio_stream(OakDecoder probe, int index,
oakcodec_audio_stream_info *out);
/* ---- Decode session ------------------------------------------------------- */
/**
* @brief Create a closed decoder handle (count 1).
*/
OAKCODEC_API OakDecoder oakcodec_decoder_init(void);
/**
* @brief Release one reference to a decoder. No-op on NULL/empty.
*/
OAKCODEC_API void oakcodec_decoder_free(OakDecoder *decoder);
/**
* @brief Open `filename`'s stream `stream_index` for decoding.
*
* The decoder implementation is chosen automatically from the probe
* results. Opening an already-open decoder on the same stream is a
* successful no-op.
*
* @return OAKCODEC_OK on success, OAKCODEC_E_NOT_FOUND when the file
* does not exist, OAKCODEC_E_FAILED otherwise (see
* oakcodec_decoder_last_error()).
*/
OAKCODEC_API int oakcodec_decoder_open(OakDecoder decoder, const char *filename,
int stream_index);
/** @brief Close the current stream (safe when closed). */
OAKCODEC_API int oakcodec_decoder_close(OakDecoder decoder);
/** @brief 1 when a stream is open, 0 otherwise. */
OAKCODEC_API int oakcodec_decoder_is_open(OakDecoder decoder);
/**
* @brief Decode the video frame at `numerator/denominator` seconds.
*
* Before the start of the footage the first frame is returned, after the
* end the last frame.
*
* @return A frame handle with reference count 1 (caller releases), or an
* empty handle (ctx == NULL) on error/EOF — check
* oakcodec_decoder_last_error().
*/
OAKCODEC_API OakFrame oakcodec_decoder_decode_video(OakDecoder decoder, int numerator,
int denominator);
/**
* @brief Decode audio into a float buffer.
*
* Decodes the interleaved audio covering [in, out) seconds (rational
* pairs), resampled/laid out to `sample_rate`/`channel_layout`.
* `buf` must hold at least `buf_frames` frames worth of interleaved
* floats.
*
* @return The number of frames written (>= 0), or a negative
* OAKCODEC_E_* code. Conform generation is NOT triggered by this
* family in the current intermediate state (no task registrar);
* media requiring a conform yields OAKCODEC_E_STATE.
*/
OAKCODEC_API int oakcodec_decoder_decode_audio(OakDecoder decoder, int in_num, int in_den,
int out_num, int out_den, int sample_rate,
uint64_t channel_layout, float *buf,
int buf_frames);
/**
* @brief Human-readable detail of the last error on this decoder
* (buf/size string getter convention).
*/
OAKCODEC_API int oakcodec_decoder_last_error(OakDecoder decoder, char *buf, int buf_size);
#ifdef __cplusplus
}
#endif
#endif //OAK_EDITOR_CODEC_DECODER_H
+192
View File
@@ -0,0 +1,192 @@
/***
Oak Video Editor - 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 OAK_EDITOR_CODEC_ENCODER_H
#define OAK_EDITOR_CODEC_ENCODER_H
#include <stdint.h>
#include "error.h"
#include "frame.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @file encoder.h
* @brief C ABI for oakcodec media encoders (olive::Encoder and its
* FFmpeg/OIIO implementations).
*
* Handles follow the neutral by-value convention documented in frame.h.
* The workflow is: fill an oakcodec_encoding_params POD (all fields,
* zeroed = disabled) -> oakcodec_encoder_init() ->
* oakcodec_encoder_open() -> oakcodec_encoder_write_*() ->
* oakcodec_encoder_flush(). Encoder-specific options
* (e.g. "crf" = "18") go through oakcodec_encoder_set_video_option()
* between init and open.
*
* Enum int fields carry the engine's own enum values
* (olive::ExportFormat::Format, olive::ExportCodec::Codec,
* OakPixelFormat, olive::VideoParams::Interlacing,
* olive::core::SampleFormat::Format) — the same values
* oakengine/encoding.h documents.
*/
typedef struct OakEncoder {
void *ctx; /**< Opaque pointer to the reference-counted object. */
void (*addref)(void *ctx); /**< Atomically increments the count. */
void (*release)(void *ctx); /**< Decrements the count, destroys at 0. */
uint32_t abi_version; /**< OAKCODEC_ABI_VERSION. */
} OakEncoder;
/** @brief olive::VideoParams::Interlacing values. */
#define OAKCODEC_INTERLACE_NONE 0
#define OAKCODEC_INTERLACE_TOP_FIRST 1
#define OAKCODEC_INTERLACE_BOTTOM_FIRST 2
/** @brief EncodingParams::VideoScalingMethod values. */
#define OAKCODEC_ENCODING_SCALING_FIT 0
#define OAKCODEC_ENCODING_SCALING_STRETCH 1
#define OAKCODEC_ENCODING_SCALING_CROP 2
/**
* @brief Flattened encoding parameters (olive::EncodingParams).
*
* A zeroed struct describes an all-tracks-disabled configuration. The
* filename (and image-sequence "[#####]" template when
* video_is_image_sequence is set) lives in `filename`.
* video_time_base_* is the frame duration (frame rate flipped), matching
* oak_video_params' convention.
*/
typedef struct oakcodec_encoding_params {
char filename[1024];
int format; /**< olive::ExportFormat::Format. */
int video_enabled; /**< 1/0. */
int video_codec; /**< olive::ExportCodec::Codec. */
int video_width;
int video_height;
int video_time_base_num; /**< Frame duration numerator. */
int video_time_base_den;
int video_pixel_format; /**< OakPixelFormat (delivery format). */
int video_interlacing; /**< OAKCODEC_INTERLACE_*. */
int video_pixel_aspect_num;
int video_pixel_aspect_den;
int64_t video_bit_rate; /**< bit/s, 0 = codec default. */
int64_t video_min_bit_rate;
int64_t video_max_bit_rate;
int64_t video_buffer_size; /**< bytes. */
int video_threads; /**< 0 = auto. */
char video_pix_fmt[64]; /**< Encoded pixel format name ("yuv420p"). */
int video_is_image_sequence; /**< 1/0. */
int video_scaling_method; /**< OAKCODEC_ENCODING_SCALING_*. */
int audio_enabled; /**< 1/0. */
int audio_codec; /**< olive::ExportCodec::Codec. */
int audio_sample_rate;
uint64_t audio_channel_layout; /**< ffmpeg-style channel mask. */
int audio_sample_format; /**< olive::core::SampleFormat::Format. */
int64_t audio_bit_rate; /**< bit/s. */
int subtitles_enabled; /**< 1/0. */
int subtitles_codec; /**< olive::ExportCodec::Codec. */
int subtitles_are_sidecar; /**< 1/0. */
int subtitles_sidecar_format; /**< olive::ExportFormat::Format. */
/** Output OCIO colorspace name; empty = reference space (no transform). */
char color_transform_output[256];
int export_length_num; /**< Export length in seconds (rational). */
int export_length_den;
} oakcodec_encoding_params;
/**
* @brief Create an encoder for `params` (count 1).
*
* The implementation (FFmpeg/OIIO) is chosen from params.format and the
* enabled tracks. The file is NOT opened yet. Returns an empty handle
* (ctx == NULL) when the configuration is invalid.
*/
OAKCODEC_API OakEncoder oakcodec_encoder_init(const oakcodec_encoding_params *params);
/** @brief Release one reference to an encoder. No-op on NULL/empty. */
OAKCODEC_API void oakcodec_encoder_free(OakEncoder *encoder);
/**
* @brief Set an encoder-specific video option (e.g. "crf" = "18").
*
* Only valid between init and open.
*
* @return OAKCODEC_OK, or OAKCODEC_E_STATE when already open.
*/
OAKCODEC_API int oakcodec_encoder_set_video_option(OakEncoder encoder, const char *key,
const char *value);
/**
* @brief Open the output file and write stream headers.
*
* @return OAKCODEC_OK, OAKCODEC_E_STATE (already open), or
* OAKCODEC_E_FAILED (see oakcodec_encoder_last_error()).
*/
OAKCODEC_API int oakcodec_encoder_open(OakEncoder encoder);
/**
* @brief Encode one video frame.
*
* The frame's parameters must match the encoding parameters (the encoder
* converts the delivery pixel format to the encoded one internally).
*/
OAKCODEC_API int oakcodec_encoder_write_video(OakEncoder encoder, OakFrame frame);
/**
* @brief Encode interleaved float audio samples.
*
* @param samples frame_count * channel_count interleaved floats.
* @return OAKCODEC_OK or a negative OAKCODEC_E_* code.
*/
OAKCODEC_API int oakcodec_encoder_write_audio(OakEncoder encoder, const float *samples,
int frame_count);
/**
* @brief Encode one subtitle entry (times in seconds).
*/
OAKCODEC_API int oakcodec_encoder_write_subtitle(OakEncoder encoder, const char *text,
double in_seconds, double out_seconds);
/**
* @brief Flush the encoders, write the trailer and close the file.
*
* Idempotent; after a successful flush the encoder cannot be written to
* (write calls return OAKCODEC_E_STATE).
*/
OAKCODEC_API int oakcodec_encoder_flush(OakEncoder encoder);
/**
* @brief Human-readable detail of the last error on this encoder
* (buf/size string getter convention).
*/
OAKCODEC_API int oakcodec_encoder_last_error(OakEncoder encoder, char *buf, int buf_size);
#ifdef __cplusplus
}
#endif
#endif //OAK_EDITOR_CODEC_ENCODER_H
+61
View File
@@ -0,0 +1,61 @@
/***
Oak Video Editor - 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 OAK_EDITOR_CODEC_ERROR_H
#define OAK_EDITOR_CODEC_ERROR_H
/**
* @brief Status and error codes shared by all oakcodec C API families.
*
* Return-code convention (mirrors the other split modules):
* 0 (OAKCODEC_OK) on success, a negative OAKCODEC_E_* error code on
* failure. String getters return the required buffer size in bytes
* (including the terminating NUL) as a non-negative value instead.
*/
#define OAKCODEC_OK 0 /**< Success. */
#define OAKCODEC_E_INVALID (-1) /**< NULL handle or invalid argument. */
#define OAKCODEC_E_STATE (-2) /**< Call not valid in the current state. */
#define OAKCODEC_E_FAILED (-3) /**< The underlying operation failed. */
#define OAKCODEC_E_NOT_FOUND (-4) /**< Index out of range / entry not found. */
#define OAKCODEC_E_NOMEM (-5) /**< Allocation failed. */
#define OAKCODEC_E_CANCELLED (-6) /**< The operation was cancelled. */
/**
* @brief Current ABI version stamped into every oakcodec handle.
*
* Bump whenever the handle layout or the semantics of any exported
* function change incompatibly. Consumers should compare a handle's
* abi_version field against the value they were compiled with before
* dereferencing ctx.
*/
#define OAKCODEC_ABI_VERSION 1
/**
* @brief Export macro for the oakcodec C ABI.
*
* oakcodec is built with -fvisibility=hidden (01 §1 rule 5): only the
* oakcodec_* functions marked with this macro leave the shared library.
* This also keeps codec-internal C++ classes (whose olive::* names may
* collide with transition stubs inside other modules) from participating
* in cross-library weak-symbol coalescing.
*/
#define OAKCODEC_API __attribute__((visibility("default")))
#endif //OAK_EDITOR_CODEC_ERROR_H
+162
View File
@@ -0,0 +1,162 @@
/***
Oak Video Editor - 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 OAK_EDITOR_CODEC_FRAME_H
#define OAK_EDITOR_CODEC_FRAME_H
#include <stdint.h>
#include "common/videoparams.h"
#include "error.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @file frame.h
* @brief C ABI for the oakcodec frame object (olive::Frame), a CPU pixel
* buffer plus an OakVideoParams parameter set.
*
* Handle convention (all oakcodec families): neutral by-value handles with
* the same four fields as oakcommon (see oakcommon's common/handle.h):
*
* typedef struct OakFrame {
* void *ctx; // opaque, points to the impl
* void (*addref)(void *ctx); // atomic +1, owner-DLL code
* void (*release)(void *ctx); // atomic -1, destroys at 0
* uint32_t abi_version; // OAKCODEC_ABI_VERSION
* } OakFrame;
*
* oakcodec_frame_init*() returns a handle whose underlying object has
* reference count 1. Copying the struct copies the pointer, not the
* count: call handle.addref(handle.ctx) for every additional long-lived
* copy and handle.release(handle.ctx) (or oakcodec_frame_free()) when
* done with each copy. Functions that only use a handle take it BY
* VALUE; an empty handle (ctx == NULL) is reported as
* OAKCODEC_E_INVALID. oakcodec_frame_free() takes a pointer so it can
* null out the caller's ctx; NULL and ctx == NULL are no-ops.
*/
typedef struct OakFrame {
void *ctx; /**< Opaque pointer to the reference-counted object. */
void (*addref)(void *ctx); /**< Atomically increments the count. */
void (*release)(void *ctx); /**< Decrements the count, destroys at 0. */
uint32_t abi_version; /**< OAKCODEC_ABI_VERSION. */
} OakFrame;
/**
* @brief Create an empty frame with default (invalid) video parameters.
*
* @return Handle with reference count 1; ctx is NULL on allocation
* failure.
*/
OAKCODEC_API OakFrame oakcodec_frame_init(void);
/**
* @brief Create a frame with a copy of the given parameter set.
*
* The params handle is addref'd internally; the caller keeps its own
* reference. The frame is not allocated; call oakcodec_frame_allocate().
*
* @return Handle with reference count 1; ctx is NULL on failure.
*/
OAKCODEC_API OakFrame oakcodec_frame_init_with_params(OakVideoParams params);
/**
* @brief Release one reference to a frame.
*
* Convenience wrapper around handle.release(handle.ctx); nulls ctx
* afterwards. No-op when frame is NULL or frame->ctx is NULL.
*/
OAKCODEC_API void oakcodec_frame_free(OakFrame *frame);
/**
* @brief Get a copy of the frame's parameter set.
*
* @param out Receives an addref'd OakVideoParams; the caller must release
* it with oakcommon_videoparams_free().
* @return OAKCODEC_OK, or OAKCODEC_E_INVALID for bad arguments.
*/
OAKCODEC_API int oakcodec_frame_get_params(OakFrame frame, OakVideoParams *out);
/**
* @brief Replace the frame's parameter set (the handle is addref'd
* internally). Recomputes the line sizes; does not reallocate the
* buffer.
*/
OAKCODEC_API int oakcodec_frame_set_params(OakFrame frame, OakVideoParams params);
/**
* @brief Allocate the pixel buffer from the current parameters.
*
* @return OAKCODEC_OK on success (including already-allocated),
* OAKCODEC_E_STATE when the parameters are invalid,
* OAKCODEC_E_INVALID for an empty handle.
*/
OAKCODEC_API int oakcodec_frame_allocate(OakFrame frame);
/** @brief 1 when the pixel buffer is allocated, 0 otherwise. */
OAKCODEC_API int oakcodec_frame_is_allocated(OakFrame frame);
/** @brief Writable pixel buffer, or NULL when unallocated/empty. */
OAKCODEC_API void *oakcodec_frame_data(OakFrame frame);
/** @brief Const variant of oakcodec_frame_data(). */
OAKCODEC_API const void *oakcodec_frame_const_data(OakFrame frame);
/** @brief Size of the pixel buffer in bytes (0 when unallocated). */
OAKCODEC_API int oakcodec_frame_allocated_size(OakFrame frame);
/** @brief Distance between two rows in bytes (0 when params are unset). */
OAKCODEC_API int oakcodec_frame_linesize_bytes(OakFrame frame);
/** @brief Distance between two rows in pixels. */
OAKCODEC_API int oakcodec_frame_linesize_pixels(OakFrame frame);
/* Query helpers; all return 0 / OAKCOMMON_PIXEL_FORMAT_INVALID on an
* empty handle. */
OAKCODEC_API int oakcodec_frame_width(OakFrame frame);
OAKCODEC_API int oakcodec_frame_height(OakFrame frame);
OAKCODEC_API int oakcodec_frame_format(OakFrame frame); /**< OakPixelFormat value. */
OAKCODEC_API int oakcodec_frame_channel_count(OakFrame frame);
/**
* @brief Frame timestamp as a rational number of seconds.
*
* @return OAKCODEC_OK, or OAKCODEC_E_INVALID for bad arguments.
*/
OAKCODEC_API int oakcodec_frame_get_timestamp(OakFrame frame, int *numerator,
int *denominator);
OAKCODEC_API int oakcodec_frame_set_timestamp(OakFrame frame, int numerator,
int denominator);
/**
* @brief Number of live oakcodec handle objects (debug/leak checking).
*
* Counts every boxed object created by oakcodec_*_init*() that has not
* been released yet, across all families (frame/decoder/encoder/...).
*/
OAKCODEC_API int oakcodec_debug_alive_count(void);
#ifdef __cplusplus
}
#endif
#endif //OAK_EDITOR_CODEC_FRAME_H
+140
View File
@@ -0,0 +1,140 @@
/***
Oak Video Editor - 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 OAK_EDITOR_CODEC_PROXY_H
#define OAK_EDITOR_CODEC_PROXY_H
#include <stdint.h>
#include "error.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @file proxy.h
* @brief C ABI for the oakcodec proxy generation singleton
* (olive::ProxyManager).
*
* Interim state (pre-M8): actual transcodes are delegated to the global
* task submit callback (see task.h). While no callback is registered,
* oakcodec_proxy_get_or_start() reports the proxy as missing instead of
* starting background work.
*/
#define OAKCODEC_PROXY_STATE_MISSING 0
#define OAKCODEC_PROXY_STATE_GENERATING 1
#define OAKCODEC_PROXY_STATE_READY 2
#define OAKCODEC_PROXY_STATE_FAILED 3
/**
* @brief POD proxy generation parameters (olive::ProxyManager::ProxyParams).
*
* divider: source resolution divider (1 = use absolute width/height,
* 2/4/8 = fraction of the source resolution). extension/preset are the
* ffmpeg output container and encoder preset (e.g. "mp4"/"veryfast").
*/
typedef struct oakcodec_proxy_params {
int width;
int height;
int divider;
int version;
int crf;
int include_audio; /**< 1/0. */
char extension[32];
char preset[32];
} oakcodec_proxy_params;
typedef struct oakcodec_proxy_result {
int state; /**< OAKCODEC_PROXY_STATE_* */
char filename[1024];
} oakcodec_proxy_result;
/**
* @brief Create the ProxyManager singleton (no-op when it exists).
*/
OAKCODEC_API int oakcodec_proxy_create_instance(void);
/**
* @brief Destroy the ProxyManager singleton (no-op when absent).
*/
OAKCODEC_API int oakcodec_proxy_destroy_instance(void);
/**
* @brief Compiled-in default proxy parameters (1280x720, divider 1, mp4,
* crf 23, "veryfast", audio included). Interim state: until the config
* milestone wires a real store these do not reflect user settings.
*/
OAKCODEC_API int oakcodec_proxy_params_default(oakcodec_proxy_params *out);
/**
* @brief State of a proxy file on disk (OAKCODEC_PROXY_STATE_*;
* OAKCODEC_PROXY_STATE_MISSING for NULL/empty/absent).
*/
OAKCODEC_API int oakcodec_proxy_get_state(const char *proxy_filename);
/** @brief Human-readable string for a proxy state (buf/size getter). */
OAKCODEC_API int oakcodec_proxy_state_to_string(int state, char *buf, int buf_size);
/** @brief Proxy directory for a project cache path (buf/size getter). */
OAKCODEC_API int oakcodec_proxy_get_proxy_directory(const char *cache_path, char *buf,
int buf_size);
/**
* @brief Deterministic proxy filename for a source stream (buf/size
* getter).
*/
OAKCODEC_API int oakcodec_proxy_get_proxy_filename(const char *cache_path,
const char *source_filename,
int stream_index,
const oakcodec_proxy_params *params,
char *buf, int buf_size);
/** @brief Working (in-progress) filename of a proxy (buf/size getter). */
OAKCODEC_API int oakcodec_proxy_get_working_filename(const char *proxy_filename,
char *buf, int buf_size);
/**
* @brief Get or start generating a proxy for `source_filename`.
*
* `cache_path` is the project cache directory. On return `out->state`
* and `out->filename` describe the proxy. When a task submit callback is
* registered (task.h) and no proxy exists, generation is submitted
* synchronously before the state is re-derived; without a registrar the
* state stays OAKCODEC_PROXY_STATE_MISSING.
*/
OAKCODEC_API int oakcodec_proxy_get_or_start(const char *cache_path,
const char *source_filename, int stream_index,
const oakcodec_proxy_params *params,
oakcodec_proxy_result *out);
/**
* @brief Locate an ffmpeg executable for proxy generation (buf/size
* getter; empty string when none is found).
*/
OAKCODEC_API int oakcodec_proxy_find_ffmpeg(const char *configured_path, char *buf,
int buf_size);
#ifdef __cplusplus
}
#endif
#endif //OAK_EDITOR_CODEC_PROXY_H
+113
View File
@@ -0,0 +1,113 @@
/***
Oak Video Editor - 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 OAK_EDITOR_CODEC_TASK_H
#define OAK_EDITOR_CODEC_TASK_H
#include <stdint.h>
#include "error.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Background task submission hook for oakcodec (interim state).
*
* The codec module occasionally needs background work (audio conforms,
* proxy transcodes). The task system itself is split out at milestone M8;
* until then oakcodec exposes a single global submit callback. A host
* (M8: oaktask) registers a callback with oakcodec_set_task_submit_cb();
* the conform/proxy managers call it whenever they need a task.
*
* While no callback is registered, managers report the work as
* unavailable (they never crash and never block).
*/
/**
* @brief Kinds of background tasks oakcodec can request.
*/
enum OakCodecTaskKind {
OAKCODEC_TASK_CONFORM = 0, /**< Audio conform to pcm cache files. */
OAKCODEC_TASK_PROXY = 1 /**< Video proxy transcode. */
};
/**
* @brief Description of one background task request.
*
* All strings are borrowed and only valid for the duration of the
* submit call; the callback must copy anything it retains.
*
* Field usage by kind:
* - OAKCODEC_TASK_CONFORM: input_filename (source media), stream_index
* (audio stream), output_filename (final path of the FIRST channel's
* pcm file; the task derives the sibling per-channel paths and the
* ".working" temporary names from the deterministic naming rule),
* sample_rate / channel_layout / sample_format (target audio params,
* sample_format is olive::core::SampleFormat::Format as int).
* - OAKCODEC_TASK_PROXY: input_filename (source media), stream_index
* (video stream), output_filename (final proxy path; the task owns
* the ".working.mp4" temporary name and the rename on success),
* proxy_width / proxy_height (absolute target size, both 0 when the
* request is divider-based).
*/
typedef struct OakCodecTaskRequest {
int kind; /**< OakCodecTaskKind. */
const char *input_filename; /**< Source media filename. */
const char *output_filename; /**< Final destination path (see above). */
int stream_index; /**< Stream inside the source media. */
int sample_rate; /**< conform: target sample rate. */
uint64_t channel_layout; /**< conform: target channel layout mask. */
int sample_format; /**< conform: target sample format (enum as int). */
int proxy_width; /**< proxy: target width, 0 = unspecified/divider. */
int proxy_height; /**< proxy: target height, 0 = unspecified/divider. */
} OakCodecTaskRequest;
/**
* @brief Task submit callback.
*
* @return 0 (OAKCODEC_OK) if the task was accepted - either completed
* synchronously or queued; a negative OAKCODEC_E_* code if the request
* was rejected.
*/
typedef int (*oakcodec_task_submit_fn)(const OakCodecTaskRequest *req,
void *userdata);
/**
* @brief Registers (or replaces) the global task submit callback.
*
* Thread-safe. Pass cb == NULL to unregister. Interim state (pre-M8):
* nobody registers and all task-dependent work reports unavailable.
*/
OAKCODEC_API void oakcodec_set_task_submit_cb(oakcodec_task_submit_fn cb, void *userdata);
/**
* @brief Returns 1 if a submit callback is currently registered, else 0.
*
* Thread-safe.
*/
OAKCODEC_API int oakcodec_task_submit_is_registered(void);
#ifdef __cplusplus
}
#endif
#endif //OAK_EDITOR_CODEC_TASK_H
+60 -13
View File
@@ -22,24 +22,39 @@
#define OAK_EDITOR_COLORTRANSFORM_H
#include "common/error.h"
#include "common/handle.h"
#ifdef __cplusplus
namespace olive
{
class ColorTransform;
}
extern "C" {
#endif
/**
* @brief Opaque handle to a color transform description
* @brief Neutral by-value handle to a color transform description
* (olive::ColorTransform).
*
* Ownership/count semantics follow the convention in common/handle.h:
* init functions return a handle whose object has reference count 1,
* addref(ctx)/release(ctx) adjust it atomically, and release destroys
* the object at zero. abi_version is always OAKCOMMON_ABI_VERSION.
*/
typedef struct OakCommonColorTransform OakCommonColorTransform;
typedef struct OakColorTransform {
void *ctx; /**< Opaque pointer to the reference-counted object. */
void (*addref)(void *ctx); /**< Atomically increments the count. */
void (*release)(void *ctx); /**< Decrements the count, destroys at 0. */
uint32_t abi_version; /**< OAKCOMMON_ABI_VERSION. */
} OakColorTransform;
/**
* @brief Create a plain output-colorspace transform.
*
* @param output Output colorspace name. Must not be NULL.
* @return Transform handle, or NULL on failure.
* @return Handle with reference count 1; ctx is NULL on failure.
*/
OakCommonColorTransform *oakcommon_colortransform_init_output(
OakColorTransform oakcommon_colortransform_init_output(
const char *output);
/**
@@ -47,15 +62,47 @@ OakCommonColorTransform *oakcommon_colortransform_init_output(
*
* All three strings must not be NULL.
*
* @return Transform handle, or NULL on failure.
* @return Handle with reference count 1; ctx is NULL on failure.
*/
OakCommonColorTransform *oakcommon_colortransform_init_display(
OakColorTransform oakcommon_colortransform_init_display(
const char *display, const char *view, const char *look);
#ifdef __cplusplus
/**
* @brief Destroy a transform. No-op on NULL.
* @brief Copy a native olive::ColorTransform into a new handle.
*
* The source object is deep-copied; the handle does not keep any
* reference to @p src, which may be destroyed immediately afterwards.
* Only visible to C++ consumers.
*
* @return Handle with reference count 1; ctx is NULL if src is NULL or
* on allocation failure.
*/
void oakcommon_colortransform_free(OakCommonColorTransform *transform);
OakColorTransform oakcommon_colortransform_init_from_native(
const olive::ColorTransform *src);
/**
* @brief Borrow the native object behind a handle.
*
* The returned pointer is borrowed: it stays valid while the caller
* holds a reference to the handle (i.e. until the matching release).
* Only visible to C++ consumers.
*
* @return Borrowed pointer, or NULL if transform is NULL or
* transform->ctx is NULL.
*/
const olive::ColorTransform *oakcommon_colortransform_get_native(
OakColorTransform transform);
#endif
/**
* @brief Release one reference to a transform.
*
* Convenience wrapper around handle.release(handle.ctx): decrements the
* atomic reference count and destroys the object when it reaches zero.
* No-op when transform is NULL or transform->ctx is NULL.
*/
void oakcommon_colortransform_free(OakColorTransform *transform);
/**
* @brief Query whether this is a display/view/look transform.
@@ -63,7 +110,7 @@ void oakcommon_colortransform_free(OakCommonColorTransform *transform);
* @param is_display Receives the result. Must not be NULL.
* @return OAKCOMMON_OK or a negative OAKCOMMON_E_* error code.
*/
int oakcommon_colortransform_is_display(OakCommonColorTransform *transform,
int oakcommon_colortransform_is_display(OakColorTransform transform,
int *is_display);
/**
@@ -72,7 +119,7 @@ int oakcommon_colortransform_is_display(OakCommonColorTransform *transform,
* @return Required buffer size in bytes including the terminating NUL
* (non-negative), or a negative OAKCOMMON_E_* error code.
*/
int oakcommon_colortransform_get_display(OakCommonColorTransform *transform,
int oakcommon_colortransform_get_display(OakColorTransform transform,
char *buf, int buf_size);
/**
@@ -81,7 +128,7 @@ int oakcommon_colortransform_get_display(OakCommonColorTransform *transform,
* @return Required buffer size in bytes including the terminating NUL
* (non-negative), or a negative OAKCOMMON_E_* error code.
*/
int oakcommon_colortransform_get_output(OakCommonColorTransform *transform,
int oakcommon_colortransform_get_output(OakColorTransform transform,
char *buf, int buf_size);
/**
@@ -90,7 +137,7 @@ int oakcommon_colortransform_get_output(OakCommonColorTransform *transform,
* @return Required buffer size in bytes including the terminating NUL
* (non-negative), or a negative OAKCOMMON_E_* error code.
*/
int oakcommon_colortransform_get_view(OakCommonColorTransform *transform,
int oakcommon_colortransform_get_view(OakColorTransform transform,
char *buf, int buf_size);
/**
@@ -99,7 +146,7 @@ int oakcommon_colortransform_get_view(OakCommonColorTransform *transform,
* @return Required buffer size in bytes including the terminating NUL
* (non-negative), or a negative OAKCOMMON_E_* error code.
*/
int oakcommon_colortransform_get_look(OakCommonColorTransform *transform,
int oakcommon_colortransform_get_look(OakColorTransform transform,
char *buf, int buf_size);
#ifdef __cplusplus
+71 -39
View File
@@ -26,56 +26,82 @@
#endif
#include "common/error.h"
#include "common/handle.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Opaque handle to a command-line parser instance.
* @brief Neutral by-value handle to a command-line parser instance.
*
* Ownership/count semantics follow the convention in common/handle.h:
* init returns a handle whose object has reference count 1,
* addref(ctx)/release(ctx) adjust it atomically, and release destroys
* the object at zero. abi_version is always OAKCOMMON_ABI_VERSION.
*/
typedef struct OakCommonCommandLineParser OakCommonCommandLineParser;
typedef struct OakCommandLineParser {
void *ctx; /**< Opaque pointer to the reference-counted object. */
void (*addref)(void *ctx); /**< Atomically increments the count. */
void (*release)(void *ctx); /**< Decrements the count, destroys at 0. */
uint32_t abi_version; /**< OAKCOMMON_ABI_VERSION. */
} OakCommandLineParser;
/**
* @brief Opaque handle to a registered command-line option.
* @brief Neutral by-value handle to a registered command-line option.
*
* The handle wrapper is freed with oakcommon_commandlineoption_free();
* the underlying option is owned by the parser and stays valid until
* the parser is freed.
* The handle is released with oakcommon_commandlineoption_free() (or
* handle.release(handle.ctx)); the underlying option is owned by the
* parser and stays valid until the parser is destroyed. abi_version is
* always OAKCOMMON_ABI_VERSION.
*/
typedef struct OakCommonCommandLineOption OakCommonCommandLineOption;
typedef struct OakCommandLineOption {
void *ctx; /**< Opaque pointer to the reference-counted object. */
void (*addref)(void *ctx); /**< Atomically increments the count. */
void (*release)(void *ctx); /**< Decrements the count, destroys at 0. */
uint32_t abi_version; /**< OAKCOMMON_ABI_VERSION. */
} OakCommandLineOption;
/**
* @brief Opaque handle to a registered positional argument.
* @brief Neutral by-value handle to a registered positional argument.
*
* The handle wrapper is freed with
* oakcommon_commandlinepositionalargument_free(); the underlying argument
* is owned by the parser and stays valid until the parser is freed.
* The handle is released with
* oakcommon_commandlinepositionalargument_free() (or
* handle.release(handle.ctx)); the underlying argument is owned by the
* parser and stays valid until the parser is destroyed. abi_version is
* always OAKCOMMON_ABI_VERSION.
*/
typedef struct OakCommonCommandLinePositionalArgument
OakCommonCommandLinePositionalArgument;
typedef struct OakCommandLinePositionalArgument {
void *ctx; /**< Opaque pointer to the reference-counted object. */
void (*addref)(void *ctx); /**< Atomically increments the count. */
void (*release)(void *ctx); /**< Decrements the count, destroys at 0. */
uint32_t abi_version; /**< OAKCOMMON_ABI_VERSION. */
} OakCommandLinePositionalArgument;
/**
* @brief Create a command-line parser.
*
* @return Parser handle, or NULL on allocation failure.
* @return Handle with reference count 1; ctx is NULL on allocation
* failure.
*/
OakCommonCommandLineParser *oakcommon_commandlineparser_init(void);
OakCommandLineParser oakcommon_commandlineparser_init(void);
/**
* @brief Destroy a command-line parser.
* @brief Release one reference to a command-line parser.
*
* Destroys all option and positional-argument handles created from it.
* NULL is a no-op.
* Convenience wrapper around handle.release(handle.ctx): decrements the
* atomic reference count and destroys the parser (invalidating all
* option and positional-argument handles created from it) when the
* count reaches zero. No-op when parser is NULL or parser->ctx is NULL.
*/
void oakcommon_commandlineparser_free(OakCommonCommandLineParser *parser);
void oakcommon_commandlineparser_free(OakCommandLineParser *parser);
/**
* @brief Set the application name/version shown by print_help.
*
* @return OAKCOMMON_OK or a negative OAKCOMMON_E_* error code.
*/
int oakcommon_commandlineparser_set_app_info(OakCommonCommandLineParser *parser,
int oakcommon_commandlineparser_set_app_info(OakCommandLineParser parser,
const char *name,
const char *version);
@@ -88,26 +114,28 @@ int oakcommon_commandlineparser_set_app_info(OakCommonCommandLineParser *parser,
* @param takes_arg Non-zero if the option consumes the following argument.
* @param arg_placeholder Placeholder shown in help, may be NULL.
* @param hidden Non-zero to omit from help output.
* @param out_option Receives the option handle. May be NULL if unused.
* @param out_option Receives the option handle (reference count 1).
* May be NULL if unused.
*
* @return OAKCOMMON_OK or a negative OAKCOMMON_E_* error code.
*/
int oakcommon_commandlineparser_add_option(
OakCommonCommandLineParser *parser, const char *const *names, int name_count,
OakCommandLineParser parser, const char *const *names, int name_count,
const char *description, int takes_arg, const char *arg_placeholder,
int hidden, OakCommonCommandLineOption **out_option);
int hidden, OakCommandLineOption *out_option);
/**
* @brief Register a positional argument.
*
* @param out_argument Receives the argument handle. May be NULL if unused.
* @param out_argument Receives the argument handle (reference count 1).
* May be NULL if unused.
*
* @return OAKCOMMON_OK or a negative OAKCOMMON_E_* error code.
*/
int oakcommon_commandlineparser_add_positional_argument(
OakCommonCommandLineParser *parser, const char *name,
OakCommandLineParser parser, const char *name,
const char *description, int required,
OakCommonCommandLinePositionalArgument **out_argument);
OakCommandLinePositionalArgument *out_argument);
/**
* @brief Parse an argv-style argument list.
@@ -116,7 +144,7 @@ int oakcommon_commandlineparser_add_positional_argument(
*
* @return OAKCOMMON_OK or a negative OAKCOMMON_E_* error code.
*/
int oakcommon_commandlineparser_process(OakCommonCommandLineParser *parser,
int oakcommon_commandlineparser_process(OakCommandLineParser parser,
const char *const *argv, int argc);
/**
@@ -124,7 +152,7 @@ int oakcommon_commandlineparser_process(OakCommonCommandLineParser *parser,
*
* @return OAKCOMMON_OK or a negative OAKCOMMON_E_* error code.
*/
int oakcommon_commandlineparser_print_help(OakCommonCommandLineParser *parser,
int oakcommon_commandlineparser_print_help(OakCommandLineParser parser,
const char *filename);
/**
@@ -134,15 +162,17 @@ int oakcommon_commandlineparser_print_help(OakCommonCommandLineParser *parser,
*
* @return OAKCOMMON_OK or a negative OAKCOMMON_E_* error code.
*/
int oakcommon_commandlineoption_is_set(OakCommonCommandLineOption *option,
int oakcommon_commandlineoption_is_set(OakCommandLineOption option,
bool *is_set);
/**
* @brief Free an option handle wrapper.
* @brief Release one reference to an option handle.
*
* Does not unregister the option from the parser. NULL is a no-op.
* Convenience wrapper around handle.release(handle.ctx). Does not
* unregister the option from the parser. No-op when option is NULL or
* option->ctx is NULL.
*/
void oakcommon_commandlineoption_free(OakCommonCommandLineOption *option);
void oakcommon_commandlineoption_free(OakCommandLineOption *option);
/**
* @brief Get an option's argument value (two-stage string getter).
@@ -150,7 +180,7 @@ void oakcommon_commandlineoption_free(OakCommonCommandLineOption *option);
* @return Required buffer size in bytes including the terminating NUL
* (non-negative), or a negative OAKCOMMON_E_* error code.
*/
int oakcommon_commandlineoption_get_setting(OakCommonCommandLineOption *option,
int oakcommon_commandlineoption_get_setting(OakCommandLineOption option,
char *buf, int buf_size);
/**
@@ -158,7 +188,7 @@ int oakcommon_commandlineoption_get_setting(OakCommonCommandLineOption *option,
*
* @return OAKCOMMON_OK or a negative OAKCOMMON_E_* error code.
*/
int oakcommon_commandlineoption_set_setting(OakCommonCommandLineOption *option,
int oakcommon_commandlineoption_set_setting(OakCommandLineOption option,
const char *value);
/**
@@ -168,7 +198,7 @@ int oakcommon_commandlineoption_set_setting(OakCommonCommandLineOption *option,
* (non-negative), or a negative OAKCOMMON_E_* error code.
*/
int oakcommon_commandlinepositionalargument_get_setting(
OakCommonCommandLinePositionalArgument *argument, char *buf, int buf_size);
OakCommandLinePositionalArgument argument, char *buf, int buf_size);
/**
* @brief Set a positional argument's value.
@@ -176,15 +206,17 @@ int oakcommon_commandlinepositionalargument_get_setting(
* @return OAKCOMMON_OK or a negative OAKCOMMON_E_* error code.
*/
int oakcommon_commandlinepositionalargument_set_setting(
OakCommonCommandLinePositionalArgument *argument, const char *value);
OakCommandLinePositionalArgument argument, const char *value);
/**
* @brief Free a positional argument handle wrapper.
* @brief Release one reference to a positional argument handle.
*
* Does not unregister the argument from the parser. NULL is a no-op.
* Convenience wrapper around handle.release(handle.ctx). Does not
* unregister the argument from the parser. No-op when argument is NULL
* or argument->ctx is NULL.
*/
void oakcommon_commandlinepositionalargument_free(
OakCommonCommandLinePositionalArgument *argument);
OakCommandLinePositionalArgument *argument);
#ifdef __cplusplus
}
+42 -26
View File
@@ -22,12 +22,27 @@
#define OAK_EDITOR_CURRENT_H
#include "common/error.h"
#include "common/handle.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct OakCommonCurrent OakCommonCurrent;
/**
* @brief Neutral by-value handle to the process-wide Current singleton.
*
* Uses the standard handle layout (see common/handle.h) but with
* singleton semantics: ctx points to a statically allocated object that
* lives until process exit, so addref() and release() are intentionally
* no-ops and never destroy anything. abi_version is always
* OAKCOMMON_ABI_VERSION.
*/
typedef struct OakCurrent {
void *ctx; /**< Opaque pointer to the singleton object. */
void (*addref)(void *ctx); /**< No-op (singleton). */
void (*release)(void *ctx); /**< No-op (singleton). */
uint32_t abi_version; /**< OAKCOMMON_ABI_VERSION. */
} OakCurrent;
/**
* @brief Destructor callback for objects handed to Current slots.
@@ -35,23 +50,24 @@ typedef struct OakCommonCurrent OakCommonCurrent;
* Called when the slot is overwritten or cleared. May be NULL if the
* caller keeps ownership of the object.
*/
typedef void (*OakCommonDestroyFn)(void *obj);
typedef void (*OakDestroyFn)(void *obj);
/**
* @brief Return a handle to the process-wide Current singleton.
*
* The returned handle is borrowed: it is valid for the lifetime of the
* process and must not be freed with oakcommon_current_free() more
* than out of symmetry (free is a no-op for the singleton).
* The returned handle is borrowed: its ctx is valid for the lifetime of
* the process. addref/release on it are no-ops; calling
* oakcommon_current_free() is allowed for symmetry and does nothing.
*/
OakCommonCurrent *oakcommon_current_instance(void);
OakCurrent oakcommon_current_instance(void);
/**
* @brief Release a Current handle.
*
* No-op: the underlying object is a singleton. Safe to call with NULL.
* No-op: the underlying object is a singleton whose release() never
* destroys anything. Safe to call with NULL or a ctx == NULL handle.
*/
void oakcommon_current_free(OakCommonCurrent *self);
void oakcommon_current_free(OakCurrent *self);
/**
* @brief Store a pointer in a Current slot, taking over destruction.
@@ -63,16 +79,16 @@ void oakcommon_current_free(OakCommonCurrent *self);
* @param obj Opaque pointer to the external object (e.g. a
* VideoParams), or NULL to clear.
* @param destroy Optional destructor invoked when the slot is replaced.
* @return OAKCOMMON_OK on success, OAKCOMMON_E_INVALID if self is NULL.
* @return OAKCOMMON_OK on success, OAKCOMMON_E_INVALID if self.ctx is NULL.
*/
int oakcommon_current_set_video_params(OakCommonCurrent *self, void *obj,
OakCommonDestroyFn destroy);
int oakcommon_current_set_audio_params(OakCommonCurrent *self, void *obj,
OakCommonDestroyFn destroy);
int oakcommon_current_set_plugin_host(OakCommonCurrent *self, void *obj,
OakCommonDestroyFn destroy);
int oakcommon_current_set_plugin_cache(OakCommonCurrent *self, void *obj,
OakCommonDestroyFn destroy);
int oakcommon_current_set_video_params(OakCurrent self, void *obj,
OakDestroyFn destroy);
int oakcommon_current_set_audio_params(OakCurrent self, void *obj,
OakDestroyFn destroy);
int oakcommon_current_set_plugin_host(OakCurrent self, void *obj,
OakDestroyFn destroy);
int oakcommon_current_set_plugin_cache(OakCurrent self, void *obj,
OakDestroyFn destroy);
/**
* @brief Fetch the raw pointer currently stored in a slot.
@@ -82,23 +98,23 @@ int oakcommon_current_set_plugin_cache(OakCommonCurrent *self, void *obj,
*
* @param self Handle from oakcommon_current_instance().
* @param out Receives the stored pointer.
* @return OAKCOMMON_OK on success, OAKCOMMON_E_INVALID if self or out
* is NULL.
* @return OAKCOMMON_OK on success, OAKCOMMON_E_INVALID if self.ctx or
* out is NULL.
*/
int oakcommon_current_get_video_params(OakCommonCurrent *self, void **out);
int oakcommon_current_get_audio_params(OakCommonCurrent *self, void **out);
int oakcommon_current_get_plugin_host(OakCommonCurrent *self, void **out);
int oakcommon_current_get_plugin_cache(OakCommonCurrent *self, void **out);
int oakcommon_current_get_video_params(OakCurrent self, void **out);
int oakcommon_current_get_audio_params(OakCurrent self, void **out);
int oakcommon_current_get_plugin_host(OakCurrent self, void **out);
int oakcommon_current_get_plugin_cache(OakCurrent self, void **out);
/**
* @brief Query whether the session is interactive.
*
* @param self Handle from oakcommon_current_instance().
* @param out Receives 1 for interactive, 0 otherwise.
* @return OAKCOMMON_OK on success, OAKCOMMON_E_INVALID if self or out
* is NULL.
* @return OAKCOMMON_OK on success, OAKCOMMON_E_INVALID if self.ctx or
* out is NULL.
*/
int oakcommon_current_is_interactive(OakCommonCurrent *self, int *out);
int oakcommon_current_is_interactive(OakCurrent self, int *out);
#ifdef __cplusplus
}
+41 -3
View File
@@ -32,7 +32,7 @@ extern "C" {
*
* Mirrors olive::DebugLevel in src/common/src/debug.h.
*/
enum OakCommonDebugLevel {
enum OakDebugLevel {
OAKCOMMON_DEBUG_DEBUG = 0, /**< Verbose debug message. */
OAKCOMMON_DEBUG_INFO = 1, /**< Informational message. */
OAKCOMMON_DEBUG_WARNING = 2, /**< Warning message. */
@@ -46,7 +46,7 @@ enum OakCommonDebugLevel {
* De-Qt replacement for the old Qt message handler. The line is
* flushed immediately.
*
* @param level One of OakCommonDebugLevel; out-of-range values print
* @param level One of OakDebugLevel; out-of-range values print
* as "UNKNOWN".
* @param msg NUL-terminated message; NULL is treated as an empty
* string.
@@ -60,7 +60,7 @@ int oakcommon_debug_log(int level, const char *msg);
* Two-segment string getter: if buf is NULL or buf_size is too small,
* nothing is written.
*
* @param level One of OakCommonDebugLevel.
* @param level One of OakDebugLevel.
* @param buf Destination buffer, may be NULL to query the size.
* @param buf_size Size of buf in bytes.
* @return Required buffer size in bytes including the terminating NUL
@@ -68,6 +68,44 @@ int oakcommon_debug_log(int level, const char *msg);
*/
int oakcommon_debug_level_name(int level, char *buf, int buf_size);
/**
* @brief printf-style filtered log, replacing qDebug()/qInfo()/
* qWarning()/qCritical() call sites.
*
* The message is formatted with vsnprintf into a dynamically sized
* buffer (arbitrary length, no truncation, no fixed stack buffer) and
* emitted as "[LEVEL] message\n" unless @p level is below the current
* filter level (see oakcommon_log_set_level()).
*
* @param level One of OakDebugLevel; out-of-range values print
* as "UNKNOWN" and are never filtered out below FATAL.
* @param fmt printf-style format string. Must not be NULL.
* @return OAKCOMMON_OK on success, OAKCOMMON_E_INVALID if fmt is NULL,
* OAKCOMMON_E_FAILED if formatting failed.
*/
int oakcommon_log(int level, const char *fmt, ...);
/**
* @brief Set the minimum level emitted by oakcommon_log().
*
* Messages with a lower level are dropped. The default is
* OAKCOMMON_DEBUG_INFO.
*
* @param level One of OakDebugLevel.
* @return OAKCOMMON_OK on success, OAKCOMMON_E_INVALID if level is
* outside the OakDebugLevel range.
*/
int oakcommon_log_set_level(int level);
/**
* @brief Query the current minimum level emitted by oakcommon_log().
*
* @param out_level Receives one of OakDebugLevel. Must not be NULL.
* @return OAKCOMMON_OK on success, OAKCOMMON_E_INVALID if out_level is
* NULL.
*/
int oakcommon_log_get_level(int *out_level);
#ifdef __cplusplus
}
#endif
+3 -3
View File
@@ -35,7 +35,7 @@ extern "C" {
* src/common/src/dropworkflowbehavior.h; enumerator order and values
* must stay identical because the config layer persists them as ints.
*/
enum OakCommonDropWorkflowBehavior {
enum OakDropWorkflowBehavior {
OAKCOMMON_DWS_ASK = 0, /**< Ask the user every time. */
OAKCOMMON_DWS_AUTO = 1, /**< Automatically create a sequence. */
OAKCOMMON_DWS_MANUAL = 2, /**< Never create; import manually. */
@@ -43,7 +43,7 @@ enum OakCommonDropWorkflowBehavior {
};
/**
* @brief Check whether value is a valid OakCommonDropWorkflowBehavior.
* @brief Check whether value is a valid OakDropWorkflowBehavior.
*
* @param value Integer behavior value (e.g. read from config).
* @return 1 if valid, 0 otherwise (this is a predicate, not a status
@@ -57,7 +57,7 @@ int oakcommon_drop_workflow_behavior_is_valid(int value);
* Two-segment string getter: if buf is NULL or buf_size is too small,
* nothing is written. Invalid values yield "UNKNOWN".
*
* @param value One of OakCommonDropWorkflowBehavior.
* @param value One of OakDropWorkflowBehavior.
* @param buf Destination buffer, may be NULL to query the size.
* @param buf_size Size of buf in bytes.
* @return Required buffer size in bytes including the terminating NUL
+1 -1
View File
@@ -30,7 +30,7 @@
* (including the terminating NUL) as a non-negative value instead.
*/
#define OAKCOMMON_OK 0 /**< Success. */
#define OAKCOMMON_E_INVALID (-1) /**< NULL handle or invalid argument. */
#define OAKCOMMON_E_INVALID (-1) /**< Empty handle (ctx == NULL) or invalid argument. */
#define OAKCOMMON_E_STATE (-2) /**< Call not valid in the current state. */
#define OAKCOMMON_E_FAILED (-3) /**< The underlying operation failed. */
#define OAKCOMMON_E_NOT_FOUND (-4) /**< Index out of range / entry not found. */
+34 -20
View File
@@ -22,30 +22,44 @@
#define OAK_EDITOR_FILEFUNCTIONS_H
#include "common/error.h"
#include "common/handle.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Opaque handle for the filefunctions family
* @brief Neutral by-value handle for the filefunctions family
*
* File functions are stateless; the handle only exists to keep the C API
* shape uniform across oakcommon families.
* shape uniform across oakcommon families. Ownership/count semantics
* follow the convention in common/handle.h: init returns a handle whose
* (empty) object has reference count 1, addref(ctx)/release(ctx) adjust
* it atomically, and release destroys it at zero. abi_version is always
* OAKCOMMON_ABI_VERSION.
*/
typedef struct OakCommonFileFunctions OakCommonFileFunctions;
typedef struct OakFileFunctions {
void *ctx; /**< Opaque pointer to the reference-counted object. */
void (*addref)(void *ctx); /**< Atomically increments the count. */
void (*release)(void *ctx); /**< Decrements the count, destroys at 0. */
uint32_t abi_version; /**< OAKCOMMON_ABI_VERSION. */
} OakFileFunctions;
/**
* @brief Creates a filefunctions handle
*
* @return A new handle, or NULL on failure.
* @return Handle with reference count 1; ctx is NULL on failure.
*/
OakCommonFileFunctions *oakcommon_filefunctions_init(void);
OakFileFunctions oakcommon_filefunctions_init(void);
/**
* @brief Destroys a filefunctions handle (NULL is a no-op)
* @brief Releases one reference to a filefunctions handle
*
* Convenience wrapper around handle.release(handle.ctx): decrements the
* atomic reference count and destroys the object when it reaches zero.
* No-op when self is NULL or self->ctx is NULL.
*/
void oakcommon_filefunctions_free(OakCommonFileFunctions *self);
void oakcommon_filefunctions_free(OakFileFunctions *self);
/**
* @brief Returns a deterministic identifier string for a file
@@ -55,20 +69,20 @@ void oakcommon_filefunctions_free(OakCommonFileFunctions *self);
* the file does not exist.
*/
int oakcommon_filefunctions_get_unique_file_identifier(
OakCommonFileFunctions *self, const char *filename, char *buf,
OakFileFunctions self, const char *filename, char *buf,
int buf_size);
int oakcommon_filefunctions_get_configuration_location(
OakCommonFileFunctions *self, char *buf, int buf_size);
OakFileFunctions self, char *buf, int buf_size);
int oakcommon_filefunctions_get_application_path(
OakCommonFileFunctions *self, char *buf, int buf_size);
OakFileFunctions self, char *buf, int buf_size);
int oakcommon_filefunctions_get_temp_file_path(
OakCommonFileFunctions *self, char *buf, int buf_size);
OakFileFunctions self, char *buf, int buf_size);
int oakcommon_filefunctions_get_auto_recovery_root(
OakCommonFileFunctions *self, char *buf, int buf_size);
OakFileFunctions self, char *buf, int buf_size);
/**
* @brief Checks whether `source` can be copied to `dest` without
@@ -77,13 +91,13 @@ int oakcommon_filefunctions_get_auto_recovery_root(
* @param out Receives 1 (safe) or 0 (would overwrite).
*/
int oakcommon_filefunctions_can_copy_directory_without_overwriting(
OakCommonFileFunctions *self, const char *source, const char *dest,
OakFileFunctions self, const char *source, const char *dest,
int *out);
/**
* @brief Recursively copies a directory
*/
int oakcommon_filefunctions_copy_directory(OakCommonFileFunctions *self,
int oakcommon_filefunctions_copy_directory(OakFileFunctions self,
const char *source,
const char *dest, int overwrite);
@@ -93,7 +107,7 @@ int oakcommon_filefunctions_copy_directory(OakCommonFileFunctions *self,
* @param out Receives 1 (valid) or 0 (invalid).
*/
int oakcommon_filefunctions_directory_is_valid(
OakCommonFileFunctions *self, const char *dir,
OakFileFunctions self, const char *dir,
int try_to_create_if_not_exists, int *out);
/**
@@ -103,7 +117,7 @@ int oakcommon_filefunctions_directory_is_valid(
* OAKCOMMON_E_* error code.
*/
int oakcommon_filefunctions_ensure_filename_extension(
OakCommonFileFunctions *self, const char *filename,
OakFileFunctions self, const char *filename,
const char *extension, char *buf, int buf_size);
/**
@@ -114,7 +128,7 @@ int oakcommon_filefunctions_ensure_filename_extension(
* the file cannot be read.
*/
int oakcommon_filefunctions_read_file_as_string(
OakCommonFileFunctions *self, const char *filename, char *buf,
OakFileFunctions self, const char *filename, char *buf,
int buf_size);
/**
@@ -124,7 +138,7 @@ int oakcommon_filefunctions_read_file_as_string(
* OAKCOMMON_E_* error code.
*/
int oakcommon_filefunctions_get_safe_temporary_filename(
OakCommonFileFunctions *self, const char *original, char *buf,
OakFileFunctions self, const char *original, char *buf,
int buf_size);
/**
@@ -133,7 +147,7 @@ int oakcommon_filefunctions_get_safe_temporary_filename(
* @param out Receives 1 (renamed) or 0 (failed).
*/
int oakcommon_filefunctions_rename_file_allow_overwrite(
OakCommonFileFunctions *self, const char *from, const char *to,
OakFileFunctions self, const char *from, const char *to,
int *out);
/**
@@ -143,7 +157,7 @@ int oakcommon_filefunctions_rename_file_allow_overwrite(
* OAKCOMMON_E_* error code.
*/
int oakcommon_filefunctions_get_formatted_executable_for_platform(
OakCommonFileFunctions *self, const char *unformatted, char *buf,
OakFileFunctions self, const char *unformatted, char *buf,
int buf_size);
#ifdef __cplusplus
+69
View File
@@ -0,0 +1,69 @@
/***
Oak Video Editor - 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 OAK_EDITOR_HANDLE_H
#define OAK_EDITOR_HANDLE_H
#include <stdint.h>
/**
* @brief Current ABI version stamped into every oakcommon handle.
*
* Bump whenever the handle layout or the semantics of any exported
* function change incompatibly. Consumers should compare a handle's
* abi_version field against the value they were compiled with before
* dereferencing ctx.
*/
#define OAKCOMMON_ABI_VERSION 1
/**
* @brief Neutral handle convention shared by all oakcommon wrappers.
*
* Every wrapper type is a by-value struct with the same four fields:
*
* typedef struct OakXxx {
* void *ctx; // opaque, points to the impl
* void (*addref)(void *ctx); // atomic +1, owner-DLL code
* void (*release)(void *ctx); // atomic -1, destroys at 0
* uint32_t abi_version; // OAKCOMMON_ABI_VERSION
* } OakXxx;
*
* Rules:
* - oakcommon_<name>_init*() returns a handle whose underlying object
* has reference count 1.
* - Copying the struct copies the pointer, not the count: call
* handle.addref(handle.ctx) for every additional long-lived copy and
* handle.release(handle.ctx) (or the oakcommon_<name>_free()
* convenience wrapper) when done with each copy.
* - release() decrements the atomic count and destroys the underlying
* object when it reaches zero; the destructor runs in the DLL that
* created the object, so cross-DLL handing is safe.
* - The struct itself carries no ownership: it is never heap-allocated
* by the API, so it needs no destruction of its own.
* - Functions that only read a handle take it BY VALUE (OakXxx self);
* an empty handle (ctx == NULL) is reported as OAKCOMMON_E_INVALID.
* oakcommon_<name>_free() deliberately stays a pointer API
* (OakXxx *h, like av_frame_unref()/av_buffer_unref()) so it can
* null out the caller's ctx after the final release; NULL and
* ctx == NULL are no-ops. Out parameters that produce a handle
* (e.g. option/positional-argument registration) also stay pointers.
*/
#endif //OAK_EDITOR_HANDLE_H
+1 -1
View File
@@ -31,7 +31,7 @@ extern "C" {
* The numeric values must stay in sync with src/common/src/loopmode.h.
* Pure enum: no functions are needed.
*/
enum OakCommonLoopMode {
enum OakLoopMode {
OAKCOMMON_LOOP_MODE_OFF = 0, /**< Looping disabled. */
OAKCOMMON_LOOP_MODE_LOOP = 1, /**< Loop playback. */
OAKCOMMON_LOOP_MODE_CLAMP = 2 /**< Clamp at the end. */
+28 -11
View File
@@ -22,6 +22,7 @@
#define OAK_EDITOR_OCIOUTILS_H
#include "common/error.h"
#include "common/handle.h"
#ifdef __cplusplus
extern "C" {
@@ -33,7 +34,7 @@ extern "C" {
* The numeric values must stay in sync with
* olive/core/render/pixelformat.h (Format enum).
*/
enum OakCommonPixelFormat {
enum OakPixelFormat {
OAKCOMMON_PIXEL_FORMAT_INVALID = -1, /**< Invalid/unknown format. */
OAKCOMMON_PIXEL_FORMAT_U8 = 0, /**< 8-bit unsigned integer. */
OAKCOMMON_PIXEL_FORMAT_U10 = 1, /**< 10-bit unsigned integer. */
@@ -52,34 +53,50 @@ enum OakCommonPixelFormat {
* BitDepth enum: 0 = unknown, 1 = uint8, 2 = uint10, 3 = uint12,
* 4 = uint14, 5 = uint16, 6 = uint32, 7 = f16, 8 = f32 (OCIO v2).
*/
typedef struct OakCommonOCIOUtils OakCommonOCIOUtils;
/**
* @brief Neutral by-value handle for the OCIO utils family
*
* The object is stateless; the handle exists only to satisfy the C API
* lifetime contract. Ownership/count semantics follow common/handle.h:
* init returns a handle whose (empty) object has reference count 1 and
* release destroys it at zero. abi_version is always
* OAKCOMMON_ABI_VERSION.
*/
typedef struct OakOCIOUtils {
void *ctx; /**< Opaque pointer to the reference-counted object. */
void (*addref)(void *ctx); /**< Atomically increments the count. */
void (*release)(void *ctx); /**< Decrements the count, destroys at 0. */
uint32_t abi_version; /**< OAKCOMMON_ABI_VERSION. */
} OakOCIOUtils;
/**
* @brief Creates an OCIOUtils handle
*
* The object is stateless; the handle exists only to satisfy the C API
* lifetime contract. Returns NULL on failure.
* @return Handle with reference count 1; ctx is NULL on failure.
*/
OakCommonOCIOUtils *oakcommon_ocioutils_init(void);
OakOCIOUtils oakcommon_ocioutils_init(void);
/**
* @brief Destroys an OCIOUtils handle; no-op on NULL
* @brief Releases one reference to an OCIOUtils handle
*
* Convenience wrapper around handle.release(handle.ctx); no-op when
* self is NULL or self->ctx is NULL.
*/
void oakcommon_ocioutils_free(OakCommonOCIOUtils *self);
void oakcommon_ocioutils_free(OakOCIOUtils *self);
/**
* @brief Maps a native pixel format to an OCIO bit depth
*
* @param self handle from oakcommon_ocioutils_init()
* @param pixel_format one of the OakCommonPixelFormat values
* @param pixel_format one of the OakPixelFormat values
* @param out_bit_depth receives the OCIO bit depth as an int (see the
* OakCommonOCIOUtils typedef documentation); set to 0
* OakOCIOUtils typedef documentation); set to 0
* (BIT_DEPTH_UNKNOWN) for invalid formats
* @return OAKCOMMON_OK, or OAKCOMMON_E_INVALID if self or
* @return OAKCOMMON_OK, or OAKCOMMON_E_INVALID if self.ctx or
* out_bit_depth is NULL or pixel_format is not a known code
*/
int oakcommon_ocioutils_get_ocio_bit_depth_from_pixel_format(
OakCommonOCIOUtils *self, int pixel_format, int *out_bit_depth);
OakOCIOUtils self, int pixel_format, int *out_bit_depth);
#ifdef __cplusplus
}
+34 -18
View File
@@ -23,7 +23,7 @@
#include "common/error.h"
/* Reuses the OakCommonPixelFormat enum (mirroring
/* Reuses the OakPixelFormat enum (mirroring
* olive::core::PixelFormat) rather than redefining it here. */
#include "common/ocioutils.h"
@@ -41,48 +41,64 @@ extern "C" {
* 13 = STRING, 14 = PTR. OIIO >= 2.5 adds 15 = USTRINGHASH and shifts
* LASTBASE, so the exact LASTBASE value is version-dependent.
*/
typedef struct OakCommonOIIOUtils OakCommonOIIOUtils;
/**
* @brief Neutral by-value handle for the OIIO utils family
*
* The object is stateless; the handle exists only to satisfy the C API
* lifetime contract. Ownership/count semantics follow common/handle.h:
* init returns a handle whose (empty) object has reference count 1 and
* release destroys it at zero. abi_version is always
* OAKCOMMON_ABI_VERSION.
*/
typedef struct OakOIIOUtils {
void *ctx; /**< Opaque pointer to the reference-counted object. */
void (*addref)(void *ctx); /**< Atomically increments the count. */
void (*release)(void *ctx); /**< Decrements the count, destroys at 0. */
uint32_t abi_version; /**< OAKCOMMON_ABI_VERSION. */
} OakOIIOUtils;
/**
* @brief Creates an OIIOUtils handle
*
* The object is stateless; the handle exists only to satisfy the C API
* lifetime contract. Returns NULL on failure.
* @return Handle with reference count 1; ctx is NULL on failure.
*/
OakCommonOIIOUtils *oakcommon_oiioutils_init(void);
OakOIIOUtils oakcommon_oiioutils_init(void);
/**
* @brief Destroys an OIIOUtils handle; no-op on NULL
* @brief Releases one reference to an OIIOUtils handle
*
* Convenience wrapper around handle.release(handle.ctx); no-op when
* self is NULL or self->ctx is NULL.
*/
void oakcommon_oiioutils_free(OakCommonOIIOUtils *self);
void oakcommon_oiioutils_free(OakOIIOUtils *self);
/**
* @brief Maps a native pixel format to an OIIO base type
*
* @param self handle from oakcommon_oiioutils_init()
* @param pixel_format one of the OakCommonPixelFormat values
* @param pixel_format one of the OakPixelFormat values
* @param out_base_type receives the OIIO base type as an int (see the
* OakCommonOIIOUtils typedef documentation); set to 0
* OakOIIOUtils typedef documentation); set to 0
* (TypeDesc::UNKNOWN) for invalid or unmappable formats
* @return OAKCOMMON_OK, or OAKCOMMON_E_INVALID if self or out_base_type
* is NULL or pixel_format is not a known code
* @return OAKCOMMON_OK, or OAKCOMMON_E_INVALID if self.ctx or
* out_base_type is NULL or pixel_format is not a known code
*/
int oakcommon_oiioutils_get_oiio_base_type_from_format(
OakCommonOIIOUtils *self, int pixel_format, int *out_base_type);
OakOIIOUtils self, int pixel_format, int *out_base_type);
/**
* @brief Maps an OIIO base type to a native pixel format
*
* @param self handle from oakcommon_oiioutils_init()
* @param base_type an OIIO TypeDesc::BASETYPE value as an int
* @param out_pixel_format receives one of the OakCommonPixelFormat
* @param out_pixel_format receives one of the OakPixelFormat
* values; set to OAKCOMMON_PIXEL_FORMAT_INVALID for unknown or
* unmappable base types
* @return OAKCOMMON_OK, or OAKCOMMON_E_INVALID if self or
* @return OAKCOMMON_OK, or OAKCOMMON_E_INVALID if self.ctx or
* out_pixel_format is NULL or base_type is negative
*/
int oakcommon_oiioutils_get_format_from_oiio_basetype(
OakCommonOIIOUtils *self, int base_type, int *out_pixel_format);
OakOIIOUtils self, int base_type, int *out_pixel_format);
/**
* @brief Converts a PixelAspectRatio attribute value to a rational
@@ -95,11 +111,11 @@ int oakcommon_oiioutils_get_format_from_oiio_basetype(
* @param pixel_aspect_ratio the PixelAspectRatio attribute value
* @param out_numerator receives the rational numerator
* @param out_denominator receives the rational denominator
* @return OAKCOMMON_OK, or OAKCOMMON_E_INVALID if self, out_numerator
* or out_denominator is NULL
* @return OAKCOMMON_OK, or OAKCOMMON_E_INVALID if self.ctx,
* out_numerator or out_denominator is NULL
*/
int oakcommon_oiioutils_get_pixel_aspect_ratio(
OakCommonOIIOUtils *self, double pixel_aspect_ratio, int *out_numerator,
OakOIIOUtils self, double pixel_aspect_ratio, int *out_numerator,
int *out_denominator);
#ifdef __cplusplus
+55 -19
View File
@@ -22,53 +22,89 @@
#define OAK_EDITOR_SUBTITLEPARAMS_H
#include "common/error.h"
#include "common/handle.h"
#ifdef __cplusplus
namespace olive
{
class SubtitleParams;
}
extern "C" {
#endif
/**
* @brief Opaque handle to a subtitle parameter set (olive::SubtitleParams).
* @brief Neutral by-value handle to a subtitle parameter set
* (olive::SubtitleParams).
*
* Ownership/count semantics follow the convention in common/handle.h:
* init functions return a handle whose object has reference count 1,
* addref(ctx)/release(ctx) adjust it atomically, and release destroys
* the object at zero. abi_version is always OAKCOMMON_ABI_VERSION.
*/
typedef struct OakCommonSubtitleParams OakCommonSubtitleParams;
typedef struct OakSubtitleParams {
void *ctx; /**< Opaque pointer to the reference-counted object. */
void (*addref)(void *ctx); /**< Atomically increments the count. */
void (*release)(void *ctx); /**< Decrements the count, destroys at 0. */
uint32_t abi_version; /**< OAKCOMMON_ABI_VERSION. */
} OakSubtitleParams;
/**
* @brief Create an empty subtitle parameter set.
*
* @return Params handle, or NULL on allocation failure.
* @return Handle with reference count 1; ctx is NULL on allocation
* failure.
*/
OakCommonSubtitleParams *oakcommon_subtitleparams_init(void);
OakSubtitleParams oakcommon_subtitleparams_init(void);
#ifdef __cplusplus
/**
* @brief Copy a native olive::SubtitleParams into a new handle.
*
* The source object is deep-copied; the handle does not keep any
* reference to @p src, which may be destroyed immediately afterwards.
* Only visible to C++ consumers.
*
* @return Handle with reference count 1; ctx is NULL if src is NULL or
* on allocation failure.
*/
OakSubtitleParams oakcommon_subtitleparams_init_from_native(
const olive::SubtitleParams *src);
#endif
/**
* @brief Destroy a subtitle parameter set. No-op on NULL.
* @brief Release one reference to a subtitle parameter set.
*
* Convenience wrapper around handle.release(handle.ctx): decrements the
* atomic reference count and destroys the object when it reaches zero.
* No-op when params is NULL or params->ctx is NULL.
*/
void oakcommon_subtitleparams_free(OakCommonSubtitleParams *params);
void oakcommon_subtitleparams_free(OakSubtitleParams *params);
int oakcommon_subtitleparams_get_stream_index(
OakCommonSubtitleParams *params, int *index);
OakSubtitleParams params, int *index);
int oakcommon_subtitleparams_set_stream_index(
OakCommonSubtitleParams *params, int index);
int oakcommon_subtitleparams_get_enabled(OakCommonSubtitleParams *params,
OakSubtitleParams params, int index);
int oakcommon_subtitleparams_get_enabled(OakSubtitleParams params,
int *enabled);
int oakcommon_subtitleparams_set_enabled(OakCommonSubtitleParams *params,
int oakcommon_subtitleparams_set_enabled(OakSubtitleParams params,
int enabled);
/**
* @brief Query whether the set contains at least one subtitle.
*/
int oakcommon_subtitleparams_is_valid(OakCommonSubtitleParams *params,
int oakcommon_subtitleparams_is_valid(OakSubtitleParams params,
int *is_valid);
/**
* @brief Number of subtitle entries.
*/
int oakcommon_subtitleparams_count(OakCommonSubtitleParams *params,
int oakcommon_subtitleparams_count(OakSubtitleParams params,
int *count);
/**
* @brief Out time of the last subtitle (0/1 when empty).
*/
int oakcommon_subtitleparams_duration(OakCommonSubtitleParams *params,
int oakcommon_subtitleparams_duration(OakSubtitleParams params,
int *numerator, int *denominator);
/**
@@ -77,14 +113,14 @@ int oakcommon_subtitleparams_duration(OakCommonSubtitleParams *params,
* @param text Subtitle text. Must not be NULL.
* @return OAKCOMMON_OK or a negative OAKCOMMON_E_* error code.
*/
int oakcommon_subtitleparams_add_subtitle(OakCommonSubtitleParams *params,
int oakcommon_subtitleparams_add_subtitle(OakSubtitleParams params,
int in_num, int in_den, int out_num,
int out_den, const char *text);
/**
* @brief Remove all subtitle entries.
*/
int oakcommon_subtitleparams_clear(OakCommonSubtitleParams *params);
int oakcommon_subtitleparams_clear(OakSubtitleParams params);
/**
* @brief Get the time range of the subtitle at @p index.
@@ -92,7 +128,7 @@ int oakcommon_subtitleparams_clear(OakCommonSubtitleParams *params);
* @return OAKCOMMON_OK, OAKCOMMON_E_NOT_FOUND if @p index is out of range,
* or another negative OAKCOMMON_E_* error code.
*/
int oakcommon_subtitleparams_get_subtitle(OakCommonSubtitleParams *params,
int oakcommon_subtitleparams_get_subtitle(OakSubtitleParams params,
int index, int *in_num, int *in_den,
int *out_num, int *out_den);
@@ -103,7 +139,7 @@ int oakcommon_subtitleparams_get_subtitle(OakCommonSubtitleParams *params,
* (non-negative), OAKCOMMON_E_NOT_FOUND if @p index is out of
* range, or another negative OAKCOMMON_E_* error code.
*/
int oakcommon_subtitleparams_get_subtitle_text(OakCommonSubtitleParams *params,
int oakcommon_subtitleparams_get_subtitle_text(OakSubtitleParams params,
int index, char *buf,
int buf_size);
@@ -121,7 +157,7 @@ int oakcommon_subtitleparams_generate_ass_header(char *buf, int buf_size);
* @param xml NUL-terminated XML text. Must not be NULL.
* @return OAKCOMMON_OK or a negative OAKCOMMON_E_* error code.
*/
int oakcommon_subtitleparams_load_xml(OakCommonSubtitleParams *params,
int oakcommon_subtitleparams_load_xml(OakSubtitleParams params,
const char *xml);
/**
@@ -130,7 +166,7 @@ int oakcommon_subtitleparams_load_xml(OakCommonSubtitleParams *params,
* @return Required buffer size in bytes including the terminating NUL
* (non-negative), or a negative OAKCOMMON_E_* error code.
*/
int oakcommon_subtitleparams_save_xml(OakCommonSubtitleParams *params,
int oakcommon_subtitleparams_save_xml(OakSubtitleParams params,
char *buf, int buf_size);
#ifdef __cplusplus
+141 -75
View File
@@ -28,21 +28,37 @@
#include <stdint.h>
#include "common/error.h"
#include "common/handle.h"
#include "common/ocioutils.h"
#ifdef __cplusplus
namespace olive
{
class VideoParams;
}
extern "C" {
#endif
/**
* @brief Opaque handle to a video parameter set (olive::VideoParams).
* @brief Neutral by-value handle to a video parameter set
* (olive::VideoParams).
*
* Ownership/count semantics follow the convention in common/handle.h:
* init functions return a handle whose object has reference count 1,
* addref(ctx)/release(ctx) adjust it atomically, and release destroys
* the object at zero. abi_version is always OAKCOMMON_ABI_VERSION.
*/
typedef struct OakCommonVideoParams OakCommonVideoParams;
typedef struct OakVideoParams {
void *ctx; /**< Opaque pointer to the reference-counted object. */
void (*addref)(void *ctx); /**< Atomically increments the count. */
void (*release)(void *ctx); /**< Decrements the count, destroys at 0. */
uint32_t abi_version; /**< OAKCOMMON_ABI_VERSION. */
} OakVideoParams;
/**
* @brief Interlacing modes, mirroring olive::VideoParams::Interlacing.
*/
enum OakCommonVideoInterlacing {
enum OakVideoInterlacing {
OAKCOMMON_VIDEO_INTERLACE_NONE = 0,
OAKCOMMON_VIDEO_INTERLACED_TOP_FIRST = 1,
OAKCOMMON_VIDEO_INTERLACED_BOTTOM_FIRST = 2
@@ -51,7 +67,7 @@ enum OakCommonVideoInterlacing {
/**
* @brief Video stream types, mirroring olive::VideoParams::Type.
*/
enum OakCommonVideoType {
enum OakVideoType {
OAKCOMMON_VIDEO_TYPE_VIDEO = 0,
OAKCOMMON_VIDEO_TYPE_STILL = 1,
OAKCOMMON_VIDEO_TYPE_IMAGE_SEQUENCE = 2
@@ -60,7 +76,7 @@ enum OakCommonVideoType {
/**
* @brief Color range codes, mirroring olive::VideoParams::ColorRange.
*/
enum OakCommonVideoColorRange {
enum OakVideoColorRange {
OAKCOMMON_COLOR_RANGE_LIMITED = 0, /**< 16-235 */
OAKCOMMON_COLOR_RANGE_FULL = 1 /**< 0-255 */
};
@@ -68,17 +84,19 @@ enum OakCommonVideoColorRange {
/**
* @brief Create a default (invalid) video parameter set.
*
* @return Params handle, or NULL on allocation failure.
* @return Handle with reference count 1; ctx is NULL on allocation
* failure.
*/
OakCommonVideoParams *oakcommon_videoparams_init(void);
OakVideoParams oakcommon_videoparams_init(void);
/**
* @brief Create a video parameter set without a time base.
*
* @param pixel_format One of the OakCommonPixelFormat values.
* @return Params handle, or NULL on allocation failure.
* @param pixel_format One of the OakPixelFormat values.
* @return Handle with reference count 1; ctx is NULL on allocation
* failure.
*/
OakCommonVideoParams *oakcommon_videoparams_init_basic(
OakVideoParams oakcommon_videoparams_init_basic(
int width, int height, int pixel_format, int nb_channels,
int pixel_aspect_num, int pixel_aspect_den, int interlacing, int divider);
@@ -87,103 +105,136 @@ OakCommonVideoParams *oakcommon_videoparams_init_basic(
*
* The frame rate is derived as the flipped time base.
*
* @param pixel_format One of the OakCommonPixelFormat values.
* @return Params handle, or NULL on allocation failure.
* @param pixel_format One of the OakPixelFormat values.
* @return Handle with reference count 1; ctx is NULL on allocation
* failure.
*/
OakCommonVideoParams *oakcommon_videoparams_init_with_time_base(
OakVideoParams oakcommon_videoparams_init_with_time_base(
int width, int height, int time_base_num, int time_base_den,
int pixel_format, int nb_channels, int pixel_aspect_num,
int pixel_aspect_den, int interlacing, int divider);
#ifdef __cplusplus
/**
* @brief Destroy a video parameter set. No-op on NULL.
* @brief Copy a native olive::VideoParams into a new handle.
*
* The source object is deep-copied; the handle does not keep any
* reference to @p src, which may be destroyed immediately afterwards.
* Only visible to C++ consumers.
*
* @return Handle with reference count 1; ctx is NULL if src is NULL or
* on allocation failure.
*/
void oakcommon_videoparams_free(OakCommonVideoParams *params);
OakVideoParams oakcommon_videoparams_init_from_native(
const olive::VideoParams *src);
int oakcommon_videoparams_get_width(OakCommonVideoParams *params, int *width);
int oakcommon_videoparams_set_width(OakCommonVideoParams *params, int width);
int oakcommon_videoparams_get_height(OakCommonVideoParams *params, int *height);
int oakcommon_videoparams_set_height(OakCommonVideoParams *params, int height);
int oakcommon_videoparams_get_depth(OakCommonVideoParams *params, int *depth);
int oakcommon_videoparams_set_depth(OakCommonVideoParams *params, int depth);
int oakcommon_videoparams_get_is_3d(OakCommonVideoParams *params, int *is_3d);
/**
* @brief Borrow the native object behind a handle.
*
* The returned pointer is borrowed: it stays valid while the caller
* holds a reference to the handle (i.e. until the matching release).
* Only visible to C++ consumers.
*
* @return Borrowed pointer, or NULL if params is NULL or params->ctx is
* NULL.
*/
const olive::VideoParams *oakcommon_videoparams_get_native(
OakVideoParams params);
#endif
/**
* @brief Release one reference to a video parameter set.
*
* Convenience wrapper around handle.release(handle.ctx): decrements the
* atomic reference count and destroys the object when it reaches zero.
* No-op when params is NULL or params->ctx is NULL.
*/
void oakcommon_videoparams_free(OakVideoParams *params);
int oakcommon_videoparams_get_width(OakVideoParams params, int *width);
int oakcommon_videoparams_set_width(OakVideoParams params, int width);
int oakcommon_videoparams_get_height(OakVideoParams params, int *height);
int oakcommon_videoparams_set_height(OakVideoParams params, int height);
int oakcommon_videoparams_get_depth(OakVideoParams params, int *depth);
int oakcommon_videoparams_set_depth(OakVideoParams params, int depth);
int oakcommon_videoparams_get_is_3d(OakVideoParams params, int *is_3d);
/**
* @brief Rational getters return the value as a numerator/denominator pair.
*/
int oakcommon_videoparams_get_time_base(OakCommonVideoParams *params,
int oakcommon_videoparams_get_time_base(OakVideoParams params,
int *numerator, int *denominator);
int oakcommon_videoparams_set_time_base(OakCommonVideoParams *params,
int oakcommon_videoparams_set_time_base(OakVideoParams params,
int numerator, int denominator);
int oakcommon_videoparams_get_frame_rate(OakCommonVideoParams *params,
int oakcommon_videoparams_get_frame_rate(OakVideoParams params,
int *numerator, int *denominator);
int oakcommon_videoparams_set_frame_rate(OakCommonVideoParams *params,
int oakcommon_videoparams_set_frame_rate(OakVideoParams params,
int numerator, int denominator);
int oakcommon_videoparams_frame_rate_as_time_base(OakCommonVideoParams *params,
int oakcommon_videoparams_frame_rate_as_time_base(OakVideoParams params,
int *numerator,
int *denominator);
int oakcommon_videoparams_get_pixel_aspect_ratio(OakCommonVideoParams *params,
int oakcommon_videoparams_get_pixel_aspect_ratio(OakVideoParams params,
int *numerator,
int *denominator);
int oakcommon_videoparams_set_pixel_aspect_ratio(OakCommonVideoParams *params,
int oakcommon_videoparams_set_pixel_aspect_ratio(OakVideoParams params,
int numerator, int denominator);
/**
* @brief Format getters/setters use the OakCommonPixelFormat codes.
* @brief Format getters/setters use the OakPixelFormat codes.
*/
int oakcommon_videoparams_get_format(OakCommonVideoParams *params, int *format);
int oakcommon_videoparams_set_format(OakCommonVideoParams *params, int format);
int oakcommon_videoparams_get_channel_count(OakCommonVideoParams *params,
int oakcommon_videoparams_get_format(OakVideoParams params, int *format);
int oakcommon_videoparams_set_format(OakVideoParams params, int format);
int oakcommon_videoparams_get_channel_count(OakVideoParams params,
int *count);
int oakcommon_videoparams_set_channel_count(OakCommonVideoParams *params,
int oakcommon_videoparams_set_channel_count(OakVideoParams params,
int count);
int oakcommon_videoparams_get_interlacing(OakCommonVideoParams *params,
int oakcommon_videoparams_get_interlacing(OakVideoParams params,
int *interlacing);
int oakcommon_videoparams_set_interlacing(OakCommonVideoParams *params,
int oakcommon_videoparams_set_interlacing(OakVideoParams params,
int interlacing);
int oakcommon_videoparams_get_divider(OakCommonVideoParams *params,
int oakcommon_videoparams_get_divider(OakVideoParams params,
int *divider);
int oakcommon_videoparams_set_divider(OakCommonVideoParams *params,
int oakcommon_videoparams_set_divider(OakVideoParams params,
int divider);
int oakcommon_videoparams_get_enabled(OakCommonVideoParams *params,
int oakcommon_videoparams_get_enabled(OakVideoParams params,
int *enabled);
int oakcommon_videoparams_set_enabled(OakCommonVideoParams *params,
int oakcommon_videoparams_set_enabled(OakVideoParams params,
int enabled);
int oakcommon_videoparams_get_x(OakCommonVideoParams *params, float *x);
int oakcommon_videoparams_set_x(OakCommonVideoParams *params, float x);
int oakcommon_videoparams_get_y(OakCommonVideoParams *params, float *y);
int oakcommon_videoparams_set_y(OakCommonVideoParams *params, float y);
int oakcommon_videoparams_get_stream_index(OakCommonVideoParams *params,
int oakcommon_videoparams_get_x(OakVideoParams params, float *x);
int oakcommon_videoparams_set_x(OakVideoParams params, float x);
int oakcommon_videoparams_get_y(OakVideoParams params, float *y);
int oakcommon_videoparams_set_y(OakVideoParams params, float y);
int oakcommon_videoparams_get_stream_index(OakVideoParams params,
int *index);
int oakcommon_videoparams_set_stream_index(OakCommonVideoParams *params,
int oakcommon_videoparams_set_stream_index(OakVideoParams params,
int index);
int oakcommon_videoparams_get_video_type(OakCommonVideoParams *params,
int oakcommon_videoparams_get_video_type(OakVideoParams params,
int *type);
int oakcommon_videoparams_set_video_type(OakCommonVideoParams *params,
int oakcommon_videoparams_set_video_type(OakVideoParams params,
int type);
int oakcommon_videoparams_get_start_time(OakCommonVideoParams *params,
int oakcommon_videoparams_get_start_time(OakVideoParams params,
int64_t *start_time);
int oakcommon_videoparams_set_start_time(OakCommonVideoParams *params,
int oakcommon_videoparams_set_start_time(OakVideoParams params,
int64_t start_time);
int oakcommon_videoparams_get_duration(OakCommonVideoParams *params,
int oakcommon_videoparams_get_duration(OakVideoParams params,
int64_t *duration);
int oakcommon_videoparams_set_duration(OakCommonVideoParams *params,
int oakcommon_videoparams_set_duration(OakVideoParams params,
int64_t duration);
int oakcommon_videoparams_get_premultiplied_alpha(OakCommonVideoParams *params,
int oakcommon_videoparams_get_premultiplied_alpha(OakVideoParams params,
int *premultiplied);
int oakcommon_videoparams_set_premultiplied_alpha(OakCommonVideoParams *params,
int oakcommon_videoparams_set_premultiplied_alpha(OakVideoParams params,
int premultiplied);
int oakcommon_videoparams_get_color_range(OakCommonVideoParams *params,
int oakcommon_videoparams_get_color_range(OakVideoParams params,
int *color_range);
int oakcommon_videoparams_set_color_range(OakCommonVideoParams *params,
int oakcommon_videoparams_set_color_range(OakVideoParams params,
int color_range);
int oakcommon_videoparams_get_color_primaries(OakCommonVideoParams *params,
int oakcommon_videoparams_get_color_primaries(OakVideoParams params,
int *primaries);
int oakcommon_videoparams_set_color_primaries(OakCommonVideoParams *params,
int oakcommon_videoparams_set_color_primaries(OakVideoParams params,
int primaries);
int oakcommon_videoparams_get_color_transfer(OakCommonVideoParams *params,
int oakcommon_videoparams_get_color_transfer(OakVideoParams params,
int *transfer);
int oakcommon_videoparams_set_color_transfer(OakCommonVideoParams *params,
int oakcommon_videoparams_set_color_transfer(OakVideoParams params,
int transfer);
/**
@@ -192,29 +243,29 @@ int oakcommon_videoparams_set_color_transfer(OakCommonVideoParams *params,
* @return Required buffer size in bytes including the terminating NUL
* (non-negative), or a negative OAKCOMMON_E_* error code.
*/
int oakcommon_videoparams_get_colorspace(OakCommonVideoParams *params,
int oakcommon_videoparams_get_colorspace(OakVideoParams params,
char *buf, int buf_size);
int oakcommon_videoparams_set_colorspace(OakCommonVideoParams *params,
int oakcommon_videoparams_set_colorspace(OakVideoParams params,
const char *colorspace);
/**
* @brief Width multiplied by the pixel aspect ratio.
*/
int oakcommon_videoparams_get_square_pixel_width(OakCommonVideoParams *params,
int oakcommon_videoparams_get_square_pixel_width(OakVideoParams params,
int *width);
int oakcommon_videoparams_get_effective_width(OakCommonVideoParams *params,
int oakcommon_videoparams_get_effective_width(OakVideoParams params,
int *width);
int oakcommon_videoparams_get_effective_height(OakCommonVideoParams *params,
int oakcommon_videoparams_get_effective_height(OakVideoParams params,
int *height);
int oakcommon_videoparams_get_effective_depth(OakCommonVideoParams *params,
int oakcommon_videoparams_get_effective_depth(OakVideoParams params,
int *depth);
int oakcommon_videoparams_get_is_valid(OakCommonVideoParams *params,
int oakcommon_videoparams_get_is_valid(OakVideoParams params,
int *is_valid);
int oakcommon_videoparams_get_bytes_per_channel(OakCommonVideoParams *params,
int oakcommon_videoparams_get_bytes_per_channel(OakVideoParams params,
int *bytes);
int oakcommon_videoparams_get_bytes_per_pixel(OakCommonVideoParams *params,
int oakcommon_videoparams_get_bytes_per_pixel(OakVideoParams params,
int *bytes);
int oakcommon_videoparams_get_buffer_size(OakCommonVideoParams *params,
int oakcommon_videoparams_get_buffer_size(OakVideoParams params,
int *size);
/**
@@ -224,14 +275,14 @@ int oakcommon_videoparams_get_buffer_size(OakCommonVideoParams *params,
* set.
*/
int oakcommon_videoparams_get_time_in_timebase_units(
OakCommonVideoParams *params, int time_num, int time_den,
OakVideoParams params, int time_num, int time_den,
int64_t *timestamp);
/**
* @brief Compare two parameter sets for equality.
*/
int oakcommon_videoparams_equals(OakCommonVideoParams *params,
OakCommonVideoParams *other, int *equal);
int oakcommon_videoparams_equals(OakVideoParams params,
OakVideoParams other, int *equal);
/**
* @brief Load parameters from an XML fragment.
@@ -239,7 +290,7 @@ int oakcommon_videoparams_equals(OakCommonVideoParams *params,
* @param xml NUL-terminated XML text. Must not be NULL.
* @return OAKCOMMON_OK or a negative OAKCOMMON_E_* error code.
*/
int oakcommon_videoparams_load_xml(OakCommonVideoParams *params,
int oakcommon_videoparams_load_xml(OakVideoParams params,
const char *xml);
/**
@@ -248,7 +299,7 @@ int oakcommon_videoparams_load_xml(OakCommonVideoParams *params,
* @return Required buffer size in bytes including the terminating NUL
* (non-negative), or a negative OAKCOMMON_E_* error code.
*/
int oakcommon_videoparams_save_xml(OakCommonVideoParams *params, char *buf,
int oakcommon_videoparams_save_xml(OakVideoParams params, char *buf,
int buf_size);
/* Static helpers (no handle required). */
@@ -294,6 +345,21 @@ int oakcommon_videoparams_get_format_name(int pixel_format, char *buf,
int oakcommon_videoparams_frame_rate_to_string(int numerator, int denominator,
char *buf, int buf_size);
/**
* @brief Get bytes per channel.
*
* @return Bytes per channel.
*/
int oakcommon_videoparams_static_get_bytes_per_channel(OakPixelFormat format);
/**
* @brief Get bytes per pixel.
*
* @return Bytes per pixel.
*/
int oakcommon_videoparams_static_get_bytes_per_pixel(OakPixelFormat format,
int channels);
#ifdef __cplusplus
}
#endif
+59 -25
View File
@@ -22,26 +22,56 @@
#define OAK_EDITOR_XMLUTILS_H
#include "common/error.h"
#include "common/handle.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct OakCommonXmlReader OakCommonXmlReader;
typedef struct OakCommonXmlWriter OakCommonXmlWriter;
/**
* @brief Neutral by-value handle to a streaming XML reader.
*
* Ownership/count semantics follow the convention in common/handle.h:
* init returns a handle whose object has reference count 1,
* addref(ctx)/release(ctx) adjust it atomically, and release destroys
* the object at zero. abi_version is always OAKCOMMON_ABI_VERSION.
*/
typedef struct OakXmlReader {
void *ctx; /**< Opaque pointer to the reference-counted object. */
void (*addref)(void *ctx); /**< Atomically increments the count. */
void (*release)(void *ctx); /**< Decrements the count, destroys at 0. */
uint32_t abi_version; /**< OAKCOMMON_ABI_VERSION. */
} OakXmlReader;
/**
* @brief Neutral by-value handle to a streaming XML writer.
*
* Same ownership/count semantics as OakXmlReader.
*/
typedef struct OakXmlWriter {
void *ctx; /**< Opaque pointer to the reference-counted object. */
void (*addref)(void *ctx); /**< Atomically increments the count. */
void (*release)(void *ctx); /**< Decrements the count, destroys at 0. */
uint32_t abi_version; /**< OAKCOMMON_ABI_VERSION. */
} OakXmlWriter;
/**
* @brief Create a streaming XML reader over a complete document.
*
* @param data NUL-terminated XML text. Must not be NULL.
* @return A new reader, or NULL on failure (NULL data, out of memory).
* @return Handle with reference count 1; ctx is NULL on failure
* (NULL data, out of memory).
*/
OakCommonXmlReader *oakcommon_xml_reader_init(const char *data);
OakXmlReader oakcommon_xml_reader_init(const char *data);
/**
* @brief Destroy a reader. No-op on NULL.
* @brief Release one reference to a reader.
*
* Convenience wrapper around handle.release(handle.ctx): decrements the
* atomic reference count and destroys the object when it reaches zero.
* No-op when reader is NULL or reader->ctx is NULL.
*/
void oakcommon_xml_reader_free(OakCommonXmlReader *reader);
void oakcommon_xml_reader_free(OakXmlReader *reader);
/**
* @brief Advance until the next start element, an end element, or the end
@@ -51,7 +81,7 @@ void oakcommon_xml_reader_free(OakCommonXmlReader *reader);
* @param found Out: 1 if positioned on a start element, 0 otherwise.
* @return OAKCOMMON_OK or a negative OAKCOMMON_E_* error code.
*/
int oakcommon_xml_reader_read_next_start_element(OakCommonXmlReader *reader,
int oakcommon_xml_reader_read_next_start_element(OakXmlReader reader,
int *found);
/**
@@ -60,7 +90,7 @@ int oakcommon_xml_reader_read_next_start_element(OakCommonXmlReader *reader,
* @return Required buffer size in bytes (including NUL), or a negative
* OAKCOMMON_E_* error code.
*/
int oakcommon_xml_reader_name(OakCommonXmlReader *reader, char *buf,
int oakcommon_xml_reader_name(OakXmlReader reader, char *buf,
int buf_size);
/**
@@ -72,7 +102,7 @@ int oakcommon_xml_reader_name(OakCommonXmlReader *reader, char *buf,
* @return Required buffer size in bytes (including NUL), or a negative
* OAKCOMMON_E_* error code.
*/
int oakcommon_xml_reader_read_element_text(OakCommonXmlReader *reader,
int oakcommon_xml_reader_read_element_text(OakXmlReader reader,
char *buf, int buf_size);
/**
@@ -80,14 +110,14 @@ int oakcommon_xml_reader_read_element_text(OakCommonXmlReader *reader,
*
* @return OAKCOMMON_OK or a negative OAKCOMMON_E_* error code.
*/
int oakcommon_xml_reader_skip_current_element(OakCommonXmlReader *reader);
int oakcommon_xml_reader_skip_current_element(OakXmlReader reader);
/**
* @brief Number of attributes on the current start element.
*
* @return OAKCOMMON_OK or a negative OAKCOMMON_E_* error code.
*/
int oakcommon_xml_reader_attribute_count(OakCommonXmlReader *reader,
int oakcommon_xml_reader_attribute_count(OakXmlReader reader,
int *count);
/**
@@ -96,7 +126,7 @@ int oakcommon_xml_reader_attribute_count(OakCommonXmlReader *reader,
* @return Required buffer size in bytes (including NUL), OAKCOMMON_E_NOT_FOUND
* if @p index is out of range, or another negative OAKCOMMON_E_* code.
*/
int oakcommon_xml_reader_attribute_name(OakCommonXmlReader *reader, int index,
int oakcommon_xml_reader_attribute_name(OakXmlReader reader, int index,
char *buf, int buf_size);
/**
@@ -105,7 +135,7 @@ int oakcommon_xml_reader_attribute_name(OakCommonXmlReader *reader, int index,
* @return Required buffer size in bytes (including NUL), OAKCOMMON_E_NOT_FOUND
* if @p index is out of range, or another negative OAKCOMMON_E_* code.
*/
int oakcommon_xml_reader_attribute_value(OakCommonXmlReader *reader,
int oakcommon_xml_reader_attribute_value(OakXmlReader reader,
int index, char *buf, int buf_size);
/**
@@ -113,32 +143,36 @@ int oakcommon_xml_reader_attribute_value(OakCommonXmlReader *reader,
*
* @return OAKCOMMON_OK or a negative OAKCOMMON_E_* error code.
*/
int oakcommon_xml_reader_has_error(OakCommonXmlReader *reader,
int oakcommon_xml_reader_has_error(OakXmlReader reader,
int *has_error);
/**
* @brief Create a streaming XML writer.
*
* @return A new writer, or NULL on failure.
* @return Handle with reference count 1; ctx is NULL on failure.
*/
OakCommonXmlWriter *oakcommon_xml_writer_init(void);
OakXmlWriter oakcommon_xml_writer_init(void);
/**
* @brief Destroy a writer. No-op on NULL.
* @brief Release one reference to a writer.
*
* Convenience wrapper around handle.release(handle.ctx): decrements the
* atomic reference count and destroys the object when it reaches zero.
* No-op when writer is NULL or writer->ctx is NULL.
*/
void oakcommon_xml_writer_free(OakCommonXmlWriter *writer);
void oakcommon_xml_writer_free(OakXmlWriter *writer);
int oakcommon_xml_writer_write_start_element(OakCommonXmlWriter *writer,
int oakcommon_xml_writer_write_start_element(OakXmlWriter writer,
const char *name);
int oakcommon_xml_writer_write_attribute(OakCommonXmlWriter *writer,
int oakcommon_xml_writer_write_attribute(OakXmlWriter writer,
const char *name, const char *value);
int oakcommon_xml_writer_write_characters(OakCommonXmlWriter *writer,
int oakcommon_xml_writer_write_characters(OakXmlWriter writer,
const char *text);
int oakcommon_xml_writer_write_text_element(OakCommonXmlWriter *writer,
int oakcommon_xml_writer_write_text_element(OakXmlWriter writer,
const char *name,
const char *text);
int oakcommon_xml_writer_write_end_element(OakCommonXmlWriter *writer);
int oakcommon_xml_writer_write_end_document(OakCommonXmlWriter *writer);
int oakcommon_xml_writer_write_end_element(OakXmlWriter writer);
int oakcommon_xml_writer_write_end_document(OakXmlWriter writer);
/**
* @brief The document written so far.
@@ -146,7 +180,7 @@ int oakcommon_xml_writer_write_end_document(OakCommonXmlWriter *writer);
* @return Required buffer size in bytes (including NUL), or a negative
* OAKCOMMON_E_* error code.
*/
int oakcommon_xml_writer_output(OakCommonXmlWriter *writer, char *buf,
int oakcommon_xml_writer_output(OakXmlWriter writer, char *buf,
int buf_size);
#ifdef __cplusplus
+1 -1
View File
@@ -189,7 +189,7 @@ int oaknode_clip_get_maintain_audio_pitch(OakNodeBlock *clip, int *maintain);
int oaknode_clip_set_maintain_audio_pitch(OakNodeBlock *clip, int maintain);
/**
* @brief Loop mode, one of the OakCommonLoopMode values
* @brief Loop mode, one of the OakLoopMode values
* (olive::ClipBlock::loop_mode/set_loop_mode).
*/
int oaknode_clip_get_loop_mode(OakNodeBlock *clip, int *loop_mode);
+5 -5
View File
@@ -174,13 +174,13 @@ int oaknode_colormanager_get_default_luma_coefs(OakNodeColorManager *manager,
* colorspace) is clamped to what the active config offers
* (olive::ColorManager::get_compliant_color_space(ColorTransform, bool)).
*
* `out` receives a NEW handle owned by the caller (release with
* oakcommon_colortransform_free()). Requires a loaded config
* (OAKNODE_E_STATE otherwise).
* `out` receives a NEW by-value handle owned by the caller (reference
* count 1, release with oakcommon_colortransform_free()). Requires a
* loaded config (OAKNODE_E_STATE otherwise).
*/
int oaknode_colormanager_get_compliant_color_transform(
OakNodeColorManager *manager, const OakCommonColorTransform *transform,
int force_display, OakCommonColorTransform **out);
OakNodeColorManager *manager, OakColorTransform transform,
int force_display, OakColorTransform *out);
#ifdef __cplusplus
}
+8 -4
View File
@@ -128,20 +128,24 @@ int oaknode_sequence_get_audio_stream_count(OakNodeSequence *sequence,
int *count);
/**
* @brief Video parameters at `index` as a NEW handle owned by the caller
* (release with oakcommon_videoparams_free()).
* @brief Video parameters at `index` as a NEW by-value handle owned by
* the caller (reference count 1, release with
* oakcommon_videoparams_free()).
*
* @return OAKNODE_OK, OAKNODE_E_INVALID, OAKNODE_E_NOT_FOUND or
* OAKNODE_E_NOMEM.
*/
int oaknode_sequence_get_video_params(OakNodeSequence *sequence, int index,
OakCommonVideoParams **out);
OakVideoParams *out);
/**
* @brief Replace the video parameters at `index` with a copy of `params`.
*
* @return OAKNODE_E_INVALID if sequence is NULL, params.ctx is NULL, or
* index is negative.
*/
int oaknode_sequence_set_video_params(OakNodeSequence *sequence, int index,
const OakCommonVideoParams *params);
OakVideoParams params);
/**
* @brief Audio parameters at `index` as a NEW handle owned by the caller
+116
View File
@@ -0,0 +1,116 @@
/***
Oak Video Editor - 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 OAK_EDITOR_RENDER_CANCELATOM_H
#define OAK_EDITOR_RENDER_CANCELATOM_H
#include <stdint.h>
#include "error.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Current ABI version stamped into every oakrender handle.
*
* Bump whenever a handle layout or the semantics of any exported function
* change incompatibly. Consumers should compare a handle's abi_version
* field against the value they were compiled with before dereferencing
* ctx.
*/
#define OAKRENDER_ABI_VERSION 1
/**
* @file cancelatom.h
* @brief C ABI for the oakrender cancellation primitive
* (olive::CancelAtom), a thread-safe cancel flag shared between a
* render/encode caller and its worker.
*
* OakCancelAtom follows the neutral by-value handle convention (see
* oakcommon's common/handle.h): oakrender_cancelatom_init() returns a
* handle whose underlying object has reference count 1, the addref and
* release function pointers adjust that count atomically (release
* destroys the object at zero), and abi_version is always
* OAKRENDER_ABI_VERSION. Copying the struct copies the pointer, not the
* count: call addref for every additional long-lived copy and release (or
* oakrender_cancelatom_free()) when done with each copy. Functions that
* only use a handle take it BY VALUE; an empty handle (ctx == NULL) is
* reported as OAKRENDER_E_INVALID.
*/
typedef struct OakCancelAtom {
void *ctx; /**< Opaque pointer to the reference-counted object. */
void (*addref)(void *ctx); /**< Atomically increments the count. */
void (*release)(void *ctx); /**< Decrements the count, destroys at 0. */
uint32_t abi_version; /**< OAKRENDER_ABI_VERSION. */
} OakCancelAtom;
/**
* @brief Create a cancellation atom in the not-cancelled state.
*
* @return Handle with reference count 1; ctx is NULL on allocation
* failure.
*/
OakCancelAtom oakrender_cancelatom_init(void);
/**
* @brief Release one reference to a cancellation atom.
*
* Convenience wrapper around atom->release(atom->ctx): decrements the
* atomic reference count and destroys the object when it reaches zero,
* then nulls atom->ctx. No-op when atom is NULL or atom->ctx is NULL.
*/
void oakrender_cancelatom_free(OakCancelAtom *atom);
/**
* @brief Set the cancel flag (CancelAtom::cancel()). Thread-safe.
*
* @return OAKRENDER_OK, or OAKRENDER_E_INVALID for an empty handle.
*/
int oakrender_cancelatom_cancel(OakCancelAtom atom);
/**
* @brief Read the cancel flag (CancelAtom::is_cancelled()).
*
* Reading a set flag also records that a consumer heard the
* cancellation; see oakrender_cancelatom_heard_cancel().
*
* @param cancelled Receives 1 when cancelled, 0 otherwise.
* @return OAKRENDER_OK, or OAKRENDER_E_INVALID for an empty handle or a
* NULL out parameter.
*/
int oakrender_cancelatom_is_cancelled(OakCancelAtom atom, int *cancelled);
/**
* @brief Whether any consumer has observed the cancel flag through
* oakrender_cancelatom_is_cancelled() (CancelAtom::heard_cancel()).
*
* @param heard Receives 1 when the cancellation was heard, 0 otherwise.
* @return OAKRENDER_OK, or OAKRENDER_E_INVALID for an empty handle or a
* NULL out parameter.
*/
int oakrender_cancelatom_heard_cancel(OakCancelAtom atom, int *heard);
#ifdef __cplusplus
}
#endif
#endif //OAK_EDITOR_RENDER_CANCELATOM_H
+18
View File
@@ -22,6 +22,7 @@
#define OAK_EDITOR_RENDER_COLOR_H
#include "error.h"
#include "renderer.h"
#ifdef __cplusplus
extern "C" {
@@ -93,6 +94,23 @@ int oakrender_color_processor_convert(OakColorProcessor *processor,
double ia, double *out_r, double *out_g,
double *out_b, double *out_a);
/**
* @brief Convert a CPU frame's pixels through the processor, in place
* (olive::ColorProcessor::convert_frame()).
*
* The frame's data buffer is rewritten through an OCIO PackedImageDesc
* view; nothing is allocated and the frame handle stays owned by the
* caller. A processor whose underlying OCIO processor is null
* (oakrender_color_processor_create() treats lookup failure as
* non-fatal) is a pass-through and returns OAKRENDER_OK, mirroring the
* C++ API.
*
* @return OAKRENDER_OK, OAKRENDER_E_INVALID for NULL/uninitialized
* arguments, or OAKRENDER_E_FAILED on an internal exception.
*/
int oakrender_color_processor_convert_frame(OakColorProcessor *processor,
OakCodecFrame *frame);
/* ---- ColorManager statics ------------------------------------------------- */
/**
+11 -11
View File
@@ -21,7 +21,7 @@
#ifndef OAKUTIL_OAKVIDEO_H
#define OAKUTIL_OAKVIDEO_H
#include <QString>
#include <string>
#include <olive/core/util/rational.h>
@@ -167,30 +167,30 @@ class ColorTransform
public:
ColorTransform() = default;
ColorTransform(const QString &output) : output_(output) {}
ColorTransform(const std::string &output) : output_(output) {}
ColorTransform(const QString &display, const QString &view,
const QString &look)
ColorTransform(const std::string &display, const std::string &view,
const std::string &look)
: output_(display), is_display_(true), view_(view), look_(look)
{
}
bool is_display() const { return is_display_; }
const QString &display() const { return output_; }
const std::string &display() const { return output_; }
const QString &output() const { return output_; }
const std::string &output() const { return output_; }
const QString &view() const { return view_; }
const std::string &view() const { return view_; }
const QString &look() const { return look_; }
const std::string &look() const { return look_; }
private:
QString output_;
std::string output_;
bool is_display_ = false;
QString view_;
QString look_;
std::string view_;
std::string look_;
};
} // namespace oak
+3 -1
View File
@@ -1,3 +1,5 @@
add_subdirectory(common)
add_subdirectory(undo)add_subdirectory(node)
add_subdirectory(undo)
add_subdirectory(node)
add_subdirectory(render)
add_subdirectory(codec)
+6
View File
@@ -0,0 +1,6 @@
add_subdirectory(src)
add_subdirectory(c_api)
if(BUILD_TESTS)
add_subdirectory(tests)
endif()
+71
View File
@@ -0,0 +1,71 @@
# oakcodec 中间态与行为变化备忘(M5)
## 中间态(等待后续里程碑收口)
1. **Task 回调注册**M8 收口):conform/proxy 的后台任务经
`include/codec/task.h` 的全局提交回调(`oakcodec_set_task_submit_cb`)。
未注册时:conform 查询返回 `k_conform_unavailable`proxy 保持
`k_proxy_missing`,不崩溃不阻塞。注册语义为同步提交(回调内完成或
排队后立即返回);`SubmitTask` 持锁调回调,回调内不可重入注册函数。
conform/proxy 任务的 working→finished 改名生命周期整体移交 M8 oaktask。
2. **Config**config 里程碑收口):`ProxyManager::proxy_params_from_config()`
返回编译期默认值(1280x720/div1/mp4/crf23/veryfast/含音频);未引入
内存态 stubffmpegencoder 当前版本已不读 Config)。
3. **纹理路径功能回退**oakrender 增补 shader-blit C API 后可恢复):
oakrender C API 无通用 shader-blitFFmpegDecoder 的 yuv2rgb GLSL 路径与
去隔行 shader 路径已删除;YUV 帧改在 CPU 上 swscale 转 RGBA 后
`oakrender_display_texture_upload`(功能保留但更慢;去隔行在纹理路径
丢失,CPU 帧路径本就不做去隔行)。Texture 零拷贝持有 hw frame 一并删除。
4. **FootageDescription 为 codec 内部结构**src/codec/src/footagedescription.h):
oaknode C API 无对应物;未实现探针缓存 XML load/save 与
`get_type_of_stream()`oaknode `Track::Type` 映射),oaknode footage
侧需要时再补。
5. **RenderMode**oakrender C API 无对应物,codec 本地 enum
decoder.hk_offline=0/k_online=1,值对齐 engine/render/rendermodes.h)。
6. **无 adapter 层**2026-08 第二轮拍板):codec 内部跨模块调用全部直调
`oakcommon_*` / `oakrender_*` C 函数,句柄(OakVideoParams/
OakColorTransform/OakCancelAtom/OakSubtitleParams)就地按值管理计数;
只有真正多处重复的转换保留文件内 static 小函数(如
fill_render_params、cancel_atom_is_cancelled)。早期的一版
src/codec/src/adapter/ 包装类已删除。
7. **XmlStreamWriter/Reader**:照 DEQT.md 用 oakcommon 的 C++ 类
src/common/src/xmlutils.h,与 oaknode/oakrender 的实践一致),未走
C API —— 决策 7 的唯一例外,记录在案。
## 行为变化(相对 Qt 版)
- Decoder 的 `index_progress` 信号 → `std::function<void(double)>`
回调(`set_index_progress_callback`);conform_ready/proxy_ready/
proxy_finished 信号删除(通知归 facade/task 系统)。
- ConformManager 无状态化:`conforming_` 列表与完成 slot 删除;
`get_conform_state` 去掉 `decoder_id` 参数;等待语义改为同步提交后
重查文件系统。
- `Encoder::write_subtitle(const SubtitleBlock*)`
`write_subtitle(const char *text, double in_seconds, double out_seconds)`
注意原实现传的是 `sub_block->length()`(时长),新调用方传 out=in+length。
- `EncodingParams::generate_matrix` 返回 `std::array<float,16>`(行主序),
原 QMatrix4x4`load/save` 的 QIODevice 版本变
`load(const std::string&)`/`save_to_string()`,预设 XML 不再含声明与
缩进(紧凑 XML,元素/属性名与顺序不变);`video_opts_` 的 XML 顺序
由 QHash 无序变为字典序。保留了 load_v1 不赋 custom_range_ 的原 bug。
- `PlanarFileDevice::open``std::vector<std::string>` + 类内
`OpenMode` 枚举(k_read_only/k_write_only),FILE* 实现。
- FFmpegDecoder 无后台 QThread(现 engine 版本已是同步 retrieve 循环)。
- 音频 decodeC API):需要 conform 的媒体在无 task 注册方时返回
`OAKCODEC_E_STATE`(不产生后台 conform)。
- `oakcodec_audio_stream_info.duration_ts` 恒 0AudioParams 不带时长)。
## 符号可见性
oakcodec 以 `-fvisibility=hidden` 编译,仅导出 `OAKCODEC_API` 标记的
C 函数(include/codec/error.h 定义宏)。必须如此:codec 内部 adapter
类(olive::VideoParams 等)与 oakcommon/oakrender 内同名弱符号会
interpose(曾在 oakcommon_videoparams_init_with_time_base 内部把
VideoParams::width() 绑进 liboakcodec 导致崩溃)。
## oakcommon 侧修复(随 M5 落地)
- `frame_to_buffer`/`buffer_to_frame` 移入 codecoiioframebridge.h
内部 C++ 函数),oakcommon 的 OIIO 映射函数保留。
- 修复 `src/common/c_api/videoparams.cpp``convert_to_olive_format`
switch 缺 break 穿透 bugU8 穿透到 f32bytes_per_pixel 返回 16)。
+7
View File
@@ -0,0 +1,7 @@
target_sources(oakcodec PRIVATE
conform.cpp
decoder.cpp
encoder.cpp
frame.cpp
proxy.cpp
)
+141
View File
@@ -0,0 +1,141 @@
/***
Oak Video Editor - 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/>.
***/
#include "codec/conform.h"
#include <algorithm>
#include <cstring>
#include <string>
#include <vector>
#include "conformmanager.h"
#include "decoder.h"
namespace
{
int string_out(const std::string &s, char *buf, int buf_size)
{
int need = static_cast<int>(s.size()) + 1;
if (buf && buf_size > 0) {
int n = std::min(static_cast<int>(s.size()), buf_size - 1);
memcpy(buf, s.data(), n);
buf[n] = '\0';
}
return need;
}
olive::core::AudioParams to_native_params(int sample_rate,
uint64_t channel_layout,
int sample_format)
{
return olive::core::AudioParams(
sample_rate, channel_layout,
static_cast<olive::core::SampleFormat::Format>(sample_format));
}
olive::Decoder::CodecStream to_native_stream(const char *source_filename,
int stream_index)
{
return olive::Decoder::CodecStream(
source_filename ? source_filename : "", stream_index, nullptr);
}
bool conform_args_valid(const char *cache_path, const char *source_filename)
{
return cache_path && *cache_path && source_filename && *source_filename;
}
} // namespace
int oakcodec_conform_create_instance(void)
{
olive::ConformManager::create_instance();
return OAKCODEC_OK;
}
int oakcodec_conform_destroy_instance(void)
{
olive::ConformManager::destroy_instance();
return OAKCODEC_OK;
}
int oakcodec_conform_get_state(const char *cache_path,
const char *source_filename, int stream_index,
int sample_rate, uint64_t channel_layout,
int sample_format, int wait)
{
if (!conform_args_valid(cache_path, source_filename))
return OAKCODEC_E_INVALID;
if (!olive::ConformManager::instance())
return OAKCODEC_E_STATE;
olive::ConformManager::Conform c =
olive::ConformManager::instance()->get_conform_state(
cache_path, to_native_stream(source_filename, stream_index),
to_native_params(sample_rate, channel_layout, sample_format),
wait != 0);
switch (c.state) {
case olive::ConformManager::k_conform_exists:
return OAKCODEC_CONFORM_EXISTS;
case olive::ConformManager::k_conform_generating:
return OAKCODEC_CONFORM_GENERATING;
case olive::ConformManager::k_conform_unavailable:
default:
return OAKCODEC_CONFORM_UNAVAILABLE;
}
}
int oakcodec_conform_filename_count(const char *cache_path,
const char *source_filename,
int stream_index, int sample_rate,
uint64_t channel_layout, int sample_format)
{
if (!conform_args_valid(cache_path, source_filename))
return 0;
// Pure path computation: never submits work.
return static_cast<int>(olive::ConformManager::get_conformed_filename(
cache_path,
to_native_stream(source_filename, stream_index),
to_native_params(sample_rate, channel_layout,
sample_format))
.size());
}
int oakcodec_conform_filename_at(const char *cache_path,
const char *source_filename,
int stream_index, int sample_rate,
uint64_t channel_layout, int sample_format,
int index, char *buf, int buf_size)
{
if (!conform_args_valid(cache_path, source_filename))
return OAKCODEC_E_INVALID;
std::vector<std::string> filenames =
olive::ConformManager::get_conformed_filename(
cache_path, to_native_stream(source_filename, stream_index),
to_native_params(sample_rate, channel_layout, sample_format));
if (index < 0 || index >= static_cast<int>(filenames.size()))
return OAKCODEC_E_NOT_FOUND;
return string_out(filenames[index], buf, buf_size);
}
+388
View File
@@ -0,0 +1,388 @@
/***
Oak Video Editor - 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/>.
***/
#include "codec/decoder.h"
#include <algorithm>
#include <cstring>
#include <string>
#include <sys/stat.h>
#include "common/loopmode.h"
#include "decoder.h"
#include "footagedescription.h"
#include "frame.h"
#include "refcounted.h"
namespace
{
struct ProbeBox {
std::string decoder_name;
olive::FootageDescription desc;
};
struct DecoderBox {
olive::DecoderPtr decoder;
std::string last_error;
std::string open_filename;
int open_stream = -1;
bool open = false;
};
ProbeBox *probe_box(void *ctx)
{
return oakcodec::handle_impl<ProbeBox>(ctx);
}
DecoderBox *decoder_box(void *ctx)
{
return oakcodec::handle_impl<DecoderBox>(ctx);
}
thread_local std::string g_probe_error;
int string_out(const std::string &s, char *buf, int buf_size)
{
int need = static_cast<int>(s.size()) + 1;
if (buf && buf_size > 0) {
int n = std::min(static_cast<int>(s.size()), buf_size - 1);
memcpy(buf, s.data(), n);
buf[n] = '\0';
}
return need;
}
bool file_exists(const char *filename)
{
struct stat st;
return filename && stat(filename, &st) == 0;
}
/**
* @brief Probe with every available decoder, returning the first valid
* description (and filling `name`).
*/
bool probe_with_any_decoder(const char *filename, std::string *name,
olive::FootageDescription *out)
{
for (const olive::DecoderPtr &d :
olive::Decoder::receive_list_of_all_decoders()) {
olive::FootageDescription desc = d->probe(filename, nullptr);
if (desc.is_valid()) {
*name = desc.decoder();
*out = desc;
return true;
}
}
return false;
}
void fill_video_info(const OakVideoParams &vp,
oakcodec_video_stream_info *out)
{
*out = {};
oakcommon_videoparams_get_stream_index(vp, &out->stream_index);
oakcommon_videoparams_get_width(vp, &out->width);
oakcommon_videoparams_get_height(vp, &out->height);
int fr_num = 0, fr_den = 0;
oakcommon_videoparams_get_frame_rate(vp, &fr_num, &fr_den);
out->frame_rate_num = fr_num;
out->frame_rate_den = fr_den;
int tb_num = 0, tb_den = 0;
oakcommon_videoparams_get_time_base(vp, &tb_num, &tb_den);
out->time_base_num = tb_num;
out->time_base_den = tb_den;
oakcommon_videoparams_get_duration(vp, &out->duration_ts);
oakcommon_videoparams_get_format(vp, &out->format);
oakcommon_videoparams_get_channel_count(vp, &out->channel_count);
oakcommon_videoparams_get_color_primaries(vp, &out->color_primaries);
oakcommon_videoparams_get_color_transfer(vp, &out->color_trc);
int interlacing = OAKCOMMON_VIDEO_INTERLACE_NONE;
oakcommon_videoparams_get_interlacing(vp, &interlacing);
out->interlaced = interlacing != OAKCOMMON_VIDEO_INTERLACE_NONE;
}
void fill_audio_info(const olive::AudioParams &ap,
oakcodec_audio_stream_info *out)
{
*out = {};
out->stream_index = ap.stream_index();
out->sample_rate = ap.sample_rate();
out->channel_layout = ap.channel_layout();
out->channel_count = ap.channel_count();
olive::Rational tb = ap.time_base();
out->time_base_num = tb.numerator();
out->time_base_den = tb.denominator();
// AudioParams carries no duration; duration_ts stays 0 (unknown).
}
} // namespace
/* ---- Probe ---------------------------------------------------------------- */
OakDecoder oakcodec_decoder_probe(const char *filename)
{
if (!filename || !*filename) {
g_probe_error = "no filename given";
return OakDecoder{};
}
if (!file_exists(filename)) {
g_probe_error = std::string("file not found: ") + filename;
return OakDecoder{};
}
OakDecoder h = oakcodec::make_handle_in_place<OakDecoder, ProbeBox>();
ProbeBox *b = probe_box(h.ctx);
if (!b) {
g_probe_error = "out of memory";
return OakDecoder{};
}
if (!probe_with_any_decoder(filename, &b->decoder_name, &b->desc)) {
g_probe_error =
std::string("no decoder recognizes this file: ") + filename;
oakcodec_decoder_free(&h);
return OakDecoder{};
}
g_probe_error.clear();
return h;
}
int oakcodec_probe_last_error(char *buf, int buf_size)
{
return string_out(g_probe_error, buf, buf_size);
}
int oakcodec_decoder_probe_decoder_name(OakDecoder probe, char *buf,
int buf_size)
{
ProbeBox *b = probe_box(probe.ctx);
if (!b)
return OAKCODEC_E_INVALID;
return string_out(b->decoder_name, buf, buf_size);
}
int oakcodec_decoder_probe_video_stream_count(OakDecoder probe)
{
ProbeBox *b = probe_box(probe.ctx);
if (!b)
return 0;
return static_cast<int>(b->desc.get_video_streams().size());
}
int oakcodec_decoder_probe_audio_stream_count(OakDecoder probe)
{
ProbeBox *b = probe_box(probe.ctx);
if (!b)
return 0;
return static_cast<int>(b->desc.get_audio_streams().size());
}
int oakcodec_decoder_probe_subtitle_stream_count(OakDecoder probe)
{
ProbeBox *b = probe_box(probe.ctx);
if (!b)
return 0;
return static_cast<int>(b->desc.get_subtitle_streams().size());
}
int oakcodec_decoder_probe_get_video_stream(OakDecoder probe, int index,
oakcodec_video_stream_info *out)
{
ProbeBox *b = probe_box(probe.ctx);
if (!b || !out)
return OAKCODEC_E_INVALID;
const auto &streams = b->desc.get_video_streams();
if (index < 0 || index >= static_cast<int>(streams.size()))
return OAKCODEC_E_NOT_FOUND;
fill_video_info(streams[static_cast<size_t>(index)], out);
return OAKCODEC_OK;
}
int oakcodec_decoder_probe_get_audio_stream(OakDecoder probe, int index,
oakcodec_audio_stream_info *out)
{
ProbeBox *b = probe_box(probe.ctx);
if (!b || !out)
return OAKCODEC_E_INVALID;
const auto &streams = b->desc.get_audio_streams();
if (index < 0 || index >= static_cast<int>(streams.size()))
return OAKCODEC_E_NOT_FOUND;
fill_audio_info(streams[static_cast<size_t>(index)], out);
return OAKCODEC_OK;
}
/* ---- Decode session -------------------------------------------------------- */
OakDecoder oakcodec_decoder_init(void)
{
return oakcodec::make_handle_in_place<OakDecoder, DecoderBox>();
}
void oakcodec_decoder_free(OakDecoder *decoder)
{
oakcodec::free_handle(decoder);
}
int oakcodec_decoder_open(OakDecoder decoder, const char *filename,
int stream_index)
{
DecoderBox *b = decoder_box(decoder.ctx);
if (!b || !filename || stream_index < 0)
return OAKCODEC_E_INVALID;
if (b->open && b->decoder) {
if (b->open_filename == filename && b->open_stream == stream_index)
return OAKCODEC_OK; // already open on this stream
b->decoder->close();
b->open = false;
}
if (!file_exists(filename)) {
b->last_error = std::string("file not found: ") + filename;
return OAKCODEC_E_NOT_FOUND;
}
std::string decoder_name;
olive::FootageDescription desc;
if (!probe_with_any_decoder(filename, &decoder_name, &desc)) {
b->last_error =
std::string("no decoder recognizes this file: ") + filename;
return OAKCODEC_E_FAILED;
}
b->decoder = olive::Decoder::create_from_id(decoder_name);
if (!b->decoder) {
b->last_error = std::string("failed to create decoder: ") + decoder_name;
return OAKCODEC_E_FAILED;
}
if (!b->decoder->open(
olive::Decoder::CodecStream(filename, stream_index, nullptr))) {
b->last_error = "failed to open stream";
b->decoder.reset();
return OAKCODEC_E_FAILED;
}
b->last_error.clear();
b->open_filename = filename;
b->open_stream = stream_index;
b->open = true;
return OAKCODEC_OK;
}
int oakcodec_decoder_close(OakDecoder decoder)
{
DecoderBox *b = decoder_box(decoder.ctx);
if (!b)
return OAKCODEC_E_INVALID;
if (b->open && b->decoder) {
b->decoder->close();
}
b->open = false;
return OAKCODEC_OK;
}
int oakcodec_decoder_is_open(OakDecoder decoder)
{
DecoderBox *b = decoder_box(decoder.ctx);
return (b && b->open) ? 1 : 0;
}
OakFrame oakcodec_decoder_decode_video(OakDecoder decoder, int numerator,
int denominator)
{
DecoderBox *b = decoder_box(decoder.ctx);
if (!b || !b->open || !b->decoder)
return OakFrame{};
olive::Decoder::RetrieveVideoParams p;
p.time = olive::Rational(numerator, denominator);
olive::FramePtr frame = b->decoder->retrieve_video_frame(p);
if (!frame) {
b->last_error = "failed to decode video frame";
return OakFrame{};
}
return oakcodec::make_handle<OakFrame>(std::move(frame));
}
int oakcodec_decoder_decode_audio(OakDecoder decoder, int in_num, int in_den,
int out_num, int out_den, int sample_rate,
uint64_t channel_layout, float *buf,
int buf_frames)
{
DecoderBox *b = decoder_box(decoder.ctx);
if (!b || (!buf && buf_frames > 0) || buf_frames < 0)
return OAKCODEC_E_INVALID;
if (!b->open || !b->decoder)
return OAKCODEC_E_STATE;
olive::AudioParams params(sample_rate, channel_layout,
olive::core::SampleFormat::f32);
olive::TimeRange range(olive::Rational(in_num, in_den),
olive::Rational(out_num, out_den));
olive::SampleBuffer samples;
olive::Decoder::RetrieveAudioStatus status = b->decoder->retrieve_audio(
samples, range, params, std::string(), OAKCOMMON_LOOP_MODE_OFF,
olive::RenderMode::k_offline);
if (status == olive::Decoder::k_waiting_for_conform) {
// Interim state (pre-M8): conform tasks require a task registrar.
b->last_error =
"audio requires a conform, but no task submit callback is "
"registered (see oakcodec_set_task_submit_cb)";
return OAKCODEC_E_STATE;
}
if (status != olive::Decoder::k_ok || !samples.is_allocated()) {
b->last_error = "failed to decode audio";
return OAKCODEC_E_FAILED;
}
int channels = samples.channel_count();
int available = static_cast<int>(samples.sample_count());
int frames = std::min(available, buf_frames);
for (int c = 0; c < channels; c++) {
const float *src = samples.data(c);
for (int i = 0; i < frames; i++) {
buf[static_cast<size_t>(i) * channels + c] = src[i];
}
}
return frames;
}
int oakcodec_decoder_last_error(OakDecoder decoder, char *buf, int buf_size)
{
DecoderBox *b = decoder_box(decoder.ctx);
if (!b)
return string_out("", buf, buf_size);
return string_out(b->last_error, buf, buf_size);
}
+272
View File
@@ -0,0 +1,272 @@
/***
Oak Video Editor - 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/>.
***/
#include "codec/encoder.h"
#include <algorithm>
#include <cstring>
#include <memory>
#include <vector>
#include "common/colortransform.h"
#include "common/videoparams.h"
#include "encoder.h"
#include "frame.h"
#include "refcounted.h"
namespace
{
constexpr int k_rgba_channel_count = 4;
struct EncoderBox {
std::unique_ptr<olive::Encoder> encoder;
olive::EncodingParams params;
bool open = false;
bool flushed = false;
};
EncoderBox *box(void *ctx)
{
return oakcodec::handle_impl<EncoderBox>(ctx);
}
int string_out(const std::string &s, char *buf, int buf_size)
{
int need = static_cast<int>(s.size()) + 1;
if (buf && buf_size > 0) {
int n = std::min(static_cast<int>(s.size()), buf_size - 1);
memcpy(buf, s.data(), n);
buf[n] = '\0';
}
return need;
}
olive::EncodingParams to_native(const oakcodec_encoding_params *p)
{
using namespace olive;
EncodingParams n;
n.set_filename(p->filename);
n.set_format(static_cast<ExportFormat::Format>(p->format));
if (p->video_enabled) {
OakVideoParams vp = oakcommon_videoparams_init_with_time_base(
p->video_width, p->video_height, p->video_time_base_num,
p->video_time_base_den, p->video_pixel_format,
k_rgba_channel_count, p->video_pixel_aspect_num,
p->video_pixel_aspect_den, p->video_interlacing, 1);
n.enable_video(vp, static_cast<ExportCodec::Codec>(p->video_codec));
oakcommon_videoparams_free(&vp);
n.set_video_bit_rate(p->video_bit_rate);
n.set_video_min_bit_rate(p->video_min_bit_rate);
n.set_video_max_bit_rate(p->video_max_bit_rate);
n.set_video_buffer_size(p->video_buffer_size);
n.set_video_threads(p->video_threads);
n.set_video_pix_fmt(p->video_pix_fmt);
n.set_video_is_image_sequence(p->video_is_image_sequence != 0);
n.set_video_scaling_method(
static_cast<EncodingParams::VideoScalingMethod>(
p->video_scaling_method));
}
if (p->audio_enabled) {
AudioParams ap(p->audio_sample_rate, p->audio_channel_layout,
static_cast<core::SampleFormat::Format>(
p->audio_sample_format));
n.enable_audio(ap, static_cast<ExportCodec::Codec>(p->audio_codec));
n.set_audio_bit_rate(p->audio_bit_rate);
}
if (p->subtitles_enabled) {
if (p->subtitles_are_sidecar) {
n.enable_sidecar_subtitles(
static_cast<ExportFormat::Format>(
p->subtitles_sidecar_format),
static_cast<ExportCodec::Codec>(p->subtitles_codec));
} else {
n.enable_subtitles(
static_cast<ExportCodec::Codec>(p->subtitles_codec));
}
}
if (p->color_transform_output[0] != '\0') {
OakColorTransform ct =
oakcommon_colortransform_init_output(p->color_transform_output);
n.set_color_transform(ct);
oakcommon_colortransform_free(&ct);
}
if (p->export_length_den != 0) {
n.set_export_length(
Rational(p->export_length_num, p->export_length_den));
}
return n;
}
} // namespace
OakEncoder oakcodec_encoder_init(const oakcodec_encoding_params *params)
{
if (!params)
return OakEncoder{};
OakEncoder h = oakcodec::make_handle_in_place<OakEncoder, EncoderBox>();
EncoderBox *b = box(h.ctx);
if (!b)
return OakEncoder{};
try {
b->params = to_native(params);
} catch (...) {
oakcodec_encoder_free(&h);
return OakEncoder{};
}
if (!b->params.is_valid()) {
oakcodec_encoder_free(&h);
return OakEncoder{};
}
return h;
}
void oakcodec_encoder_free(OakEncoder *encoder)
{
oakcodec::free_handle(encoder);
}
int oakcodec_encoder_set_video_option(OakEncoder encoder, const char *key,
const char *value)
{
EncoderBox *b = box(encoder.ctx);
if (!b || !key)
return OAKCODEC_E_INVALID;
if (b->open)
return OAKCODEC_E_STATE;
b->params.set_video_option(key, value ? value : "");
return OAKCODEC_OK;
}
int oakcodec_encoder_open(OakEncoder encoder)
{
EncoderBox *b = box(encoder.ctx);
if (!b)
return OAKCODEC_E_INVALID;
if (b->open)
return OAKCODEC_E_STATE;
b->encoder.reset(olive::Encoder::create_from_params(b->params));
if (!b->encoder)
return OAKCODEC_E_FAILED;
if (!b->encoder->open()) {
return OAKCODEC_E_FAILED;
}
b->open = true;
return OAKCODEC_OK;
}
int oakcodec_encoder_write_video(OakEncoder encoder, OakFrame frame)
{
EncoderBox *b = box(encoder.ctx);
if (!b || !frame.ctx)
return OAKCODEC_E_INVALID;
if (!b->open || b->flushed || !b->encoder)
return OAKCODEC_E_STATE;
// OakFrame boxes hold an olive::FramePtr (see c_api/frame.cpp).
auto *fp = oakcodec::handle_impl<olive::FramePtr>(frame.ctx);
if (!fp || !*fp)
return OAKCODEC_E_INVALID;
olive::Frame *f = fp->get();
return b->encoder->write_frame(*fp, f->timestamp()) ? OAKCODEC_OK
: OAKCODEC_E_FAILED;
}
int oakcodec_encoder_write_audio(OakEncoder encoder, const float *samples,
int frame_count)
{
EncoderBox *b = box(encoder.ctx);
if (!b || (!samples && frame_count > 0) || frame_count < 0)
return OAKCODEC_E_INVALID;
if (!b->open || b->flushed || !b->encoder)
return OAKCODEC_E_STATE;
const olive::AudioParams &ap = b->params.audio_params();
int channels = ap.channel_count();
if (channels <= 0)
return OAKCODEC_E_STATE;
// Deinterleave into a planar SampleBuffer.
olive::SampleBuffer buf(ap, static_cast<size_t>(frame_count));
buf.allocate();
std::vector<float> channel_data(static_cast<size_t>(frame_count));
for (int c = 0; c < channels; c++) {
for (int i = 0; i < frame_count; i++) {
channel_data[i] = samples[static_cast<size_t>(i) * channels + c];
}
buf.set(c, channel_data.data(),
static_cast<size_t>(frame_count));
}
return b->encoder->write_audio(buf) ? OAKCODEC_OK : OAKCODEC_E_FAILED;
}
int oakcodec_encoder_write_subtitle(OakEncoder encoder, const char *text,
double in_seconds, double out_seconds)
{
EncoderBox *b = box(encoder.ctx);
if (!b || !text)
return OAKCODEC_E_INVALID;
if (!b->open || b->flushed || !b->encoder)
return OAKCODEC_E_STATE;
return b->encoder->write_subtitle(text, in_seconds, out_seconds)
? OAKCODEC_OK
: OAKCODEC_E_FAILED;
}
int oakcodec_encoder_flush(OakEncoder encoder)
{
EncoderBox *b = box(encoder.ctx);
if (!b)
return OAKCODEC_E_INVALID;
if (!b->open)
return OAKCODEC_E_STATE;
if (b->flushed)
return OAKCODEC_OK;
b->encoder->close();
b->flushed = true;
return OAKCODEC_OK;
}
int oakcodec_encoder_last_error(OakEncoder encoder, char *buf, int buf_size)
{
EncoderBox *b = box(encoder.ctx);
if (!b)
return string_out("", buf, buf_size);
return string_out(b->encoder ? b->encoder->get_error() : std::string(),
buf, buf_size);
}
+198
View File
@@ -0,0 +1,198 @@
/***
Oak Video Editor - 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/>.
***/
#include "codec/frame.h"
#include <atomic>
#include "frame.h"
#include "refcounted.h"
namespace
{
// Every OakFrame box holds an olive::FramePtr: frames created here own a
// fresh olive::Frame, decoder-produced frames alias the decoder's
// shared_ptr. Unifying the box type keeps the addref/release thunks and
// the impl recovery symmetric across all OakFrame handles.
olive::Frame *impl(void *ctx)
{
auto *p = oakcodec::handle_impl<olive::FramePtr>(ctx);
return p ? p->get() : nullptr;
}
} // namespace
namespace oakcodec
{
std::atomic<int> g_alive_count{0};
void alive_inc()
{
g_alive_count.fetch_add(1, std::memory_order_relaxed);
}
void alive_dec()
{
g_alive_count.fetch_sub(1, std::memory_order_relaxed);
}
} // namespace oakcodec
int oakcodec_debug_alive_count(void)
{
return oakcodec::g_alive_count.load(std::memory_order_relaxed);
}
OakFrame oakcodec_frame_init(void)
{
return oakcodec::make_handle<OakFrame>(olive::Frame::create());
}
OakFrame oakcodec_frame_init_with_params(OakVideoParams params)
{
OakFrame h = oakcodec_frame_init();
if (h.ctx) {
impl(h.ctx)->set_video_params(params);
}
return h;
}
void oakcodec_frame_free(OakFrame *frame)
{
oakcodec::free_handle(frame);
}
int oakcodec_frame_get_params(OakFrame frame, OakVideoParams *out)
{
if (!frame.ctx || !out)
return OAKCODEC_E_INVALID;
*out = impl(frame.ctx)->video_params();
return OAKCODEC_OK;
}
int oakcodec_frame_set_params(OakFrame frame, OakVideoParams params)
{
if (!frame.ctx)
return OAKCODEC_E_INVALID;
impl(frame.ctx)->set_video_params(params);
return OAKCODEC_OK;
}
int oakcodec_frame_allocate(OakFrame frame)
{
if (!frame.ctx)
return OAKCODEC_E_INVALID;
if (!impl(frame.ctx)->allocate())
return OAKCODEC_E_STATE;
return OAKCODEC_OK;
}
int oakcodec_frame_is_allocated(OakFrame frame)
{
if (!frame.ctx)
return 0;
return impl(frame.ctx)->is_allocated() ? 1 : 0;
}
void *oakcodec_frame_data(OakFrame frame)
{
if (!frame.ctx)
return nullptr;
return impl(frame.ctx)->data();
}
const void *oakcodec_frame_const_data(OakFrame frame)
{
if (!frame.ctx)
return nullptr;
return impl(frame.ctx)->const_data();
}
int oakcodec_frame_allocated_size(OakFrame frame)
{
if (!frame.ctx)
return 0;
return impl(frame.ctx)->allocated_size();
}
int oakcodec_frame_linesize_bytes(OakFrame frame)
{
if (!frame.ctx)
return 0;
return impl(frame.ctx)->linesize_bytes();
}
int oakcodec_frame_linesize_pixels(OakFrame frame)
{
if (!frame.ctx)
return 0;
return impl(frame.ctx)->linesize_pixels();
}
int oakcodec_frame_width(OakFrame frame)
{
if (!frame.ctx)
return 0;
return impl(frame.ctx)->width();
}
int oakcodec_frame_height(OakFrame frame)
{
if (!frame.ctx)
return 0;
return impl(frame.ctx)->height();
}
int oakcodec_frame_format(OakFrame frame)
{
if (!frame.ctx)
return OAKCOMMON_PIXEL_FORMAT_INVALID;
return impl(frame.ctx)->format();
}
int oakcodec_frame_channel_count(OakFrame frame)
{
if (!frame.ctx)
return 0;
return impl(frame.ctx)->channel_count();
}
int oakcodec_frame_get_timestamp(OakFrame frame, int *numerator,
int *denominator)
{
if (!frame.ctx || !numerator || !denominator)
return OAKCODEC_E_INVALID;
const olive::core::Rational &ts = impl(frame.ctx)->timestamp();
*numerator = ts.numerator();
*denominator = ts.denominator();
return OAKCODEC_OK;
}
int oakcodec_frame_set_timestamp(OakFrame frame, int numerator,
int denominator)
{
if (!frame.ctx)
return OAKCODEC_E_INVALID;
impl(frame.ctx)->set_timestamp(
olive::core::Rational(numerator, denominator));
return OAKCODEC_OK;
}
+170
View File
@@ -0,0 +1,170 @@
/***
Oak Video Editor - 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/>.
***/
#include "codec/proxy.h"
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <string>
#include "proxymanager.h"
namespace
{
int string_out(const std::string &s, char *buf, int buf_size)
{
int need = static_cast<int>(s.size()) + 1;
if (buf && buf_size > 0) {
int n = std::min(static_cast<int>(s.size()), buf_size - 1);
memcpy(buf, s.data(), n);
buf[n] = '\0';
}
return need;
}
olive::ProxyManager::ProxyParams to_native(const oakcodec_proxy_params *p)
{
olive::ProxyManager::ProxyParams n;
if (p) {
n.width = p->width;
n.height = p->height;
n.divider = p->divider;
n.version = p->version;
n.crf = p->crf;
n.include_audio = p->include_audio != 0;
n.extension = p->extension;
n.preset = p->preset;
}
return n;
}
} // namespace
int oakcodec_proxy_create_instance(void)
{
olive::ProxyManager::create_instance();
return OAKCODEC_OK;
}
int oakcodec_proxy_destroy_instance(void)
{
olive::ProxyManager::destroy_instance();
return OAKCODEC_OK;
}
int oakcodec_proxy_params_default(oakcodec_proxy_params *out)
{
if (!out)
return OAKCODEC_E_INVALID;
olive::ProxyManager::ProxyParams n =
olive::ProxyManager::proxy_params_from_config();
*out = {};
out->width = n.width;
out->height = n.height;
out->divider = n.divider;
out->version = n.version;
out->crf = n.crf;
out->include_audio = n.include_audio ? 1 : 0;
snprintf(out->extension, sizeof(out->extension), "%s",
n.extension.c_str());
snprintf(out->preset, sizeof(out->preset), "%s", n.preset.c_str());
return OAKCODEC_OK;
}
int oakcodec_proxy_get_state(const char *proxy_filename)
{
if (!proxy_filename || !*proxy_filename)
return OAKCODEC_PROXY_STATE_MISSING;
return static_cast<int>(
olive::ProxyManager::get_proxy_state(proxy_filename));
}
int oakcodec_proxy_state_to_string(int state, char *buf, int buf_size)
{
if (state < OAKCODEC_PROXY_STATE_MISSING ||
state > OAKCODEC_PROXY_STATE_FAILED)
return OAKCODEC_E_INVALID;
return string_out(olive::ProxyManager::proxy_state_to_string(
static_cast<olive::ProxyManager::ProxyState>(state)),
buf, buf_size);
}
int oakcodec_proxy_get_proxy_directory(const char *cache_path, char *buf,
int buf_size)
{
if (!cache_path)
return OAKCODEC_E_INVALID;
return string_out(olive::ProxyManager::get_proxy_directory(cache_path),
buf, buf_size);
}
int oakcodec_proxy_get_proxy_filename(const char *cache_path,
const char *source_filename,
int stream_index,
const oakcodec_proxy_params *params,
char *buf, int buf_size)
{
if (!cache_path || !source_filename)
return OAKCODEC_E_INVALID;
return string_out(
olive::ProxyManager::get_proxy_filename(
cache_path, source_filename, stream_index, to_native(params)),
buf, buf_size);
}
int oakcodec_proxy_get_working_filename(const char *proxy_filename,
char *buf, int buf_size)
{
if (!proxy_filename)
return OAKCODEC_E_INVALID;
return string_out(
olive::ProxyManager::get_working_proxy_filename(proxy_filename),
buf, buf_size);
}
int oakcodec_proxy_get_or_start(const char *cache_path,
const char *source_filename, int stream_index,
const oakcodec_proxy_params *params,
oakcodec_proxy_result *out)
{
if (!cache_path || !source_filename || !out)
return OAKCODEC_E_INVALID;
if (!olive::ProxyManager::instance())
return OAKCODEC_E_STATE;
olive::ProxyManager::Proxy p =
olive::ProxyManager::instance()->get_or_start_proxy(
cache_path, source_filename, stream_index, to_native(params));
out->state = static_cast<int>(p.state);
snprintf(out->filename, sizeof(out->filename), "%s",
p.filename.c_str());
return OAKCODEC_OK;
}
int oakcodec_proxy_find_ffmpeg(const char *configured_path, char *buf,
int buf_size)
{
return string_out(olive::ProxyManager::find_f_fmpeg_executable(
configured_path ? configured_path : ""),
buf, buf_size);
}
+124
View File
@@ -0,0 +1,124 @@
/***
Oak Video Editor - 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 OAKCODEC_C_API_REFCOUNTED_H
#define OAKCODEC_C_API_REFCOUNTED_H
#include <atomic>
#include <cstdint>
#include <type_traits>
#include <utility>
#include "codec/error.h"
namespace oakcodec
{
/**
* @brief Heap box behind every handle's ctx pointer.
*
* Same pattern as oakcommon's c_api/refcounted.h: holds the wrapped
* object plus its atomic reference count. addref and release are emitted
* per boxed type so that the function pointers stored in a handle always
* run code from the DLL that created the object. Every box also
* participates in the oakcodec_debug_alive_count() ledger.
*/
template <typename T> struct RefCounted {
T impl;
std::atomic<uint32_t> refs;
template <typename... Args>
explicit RefCounted(Args &&...args)
: impl(std::forward<Args>(args)...)
, refs(1)
{
}
};
template <typename T> void ref_counted_addref(void *ctx)
{
auto *box = static_cast<RefCounted<T> *>(ctx);
if (box)
box->refs.fetch_add(1, std::memory_order_relaxed);
}
void alive_inc();
void alive_dec();
template <typename T> void ref_counted_release(void *ctx)
{
auto *box = static_cast<RefCounted<T> *>(ctx);
if (box && box->refs.fetch_sub(1, std::memory_order_acq_rel) == 1) {
delete box;
alive_dec();
}
}
/**
* @brief Build a by-value handle owning a freshly boxed object (count 1).
*
* On allocation failure the returned handle has ctx == NULL (all C API
* functions treat that as OAKCODEC_E_INVALID and free() as a no-op).
*/
template <typename Handle, typename T, typename... Args>
Handle make_handle_in_place(Args &&...args)
{
Handle h = {};
try {
h.ctx = new RefCounted<T>(std::forward<Args>(args)...);
alive_inc();
} catch (...) {
h.ctx = nullptr;
}
h.addref = &ref_counted_addref<T>;
h.release = &ref_counted_release<T>;
h.abi_version = OAKCODEC_ABI_VERSION;
return h;
}
template <typename Handle, typename T> Handle make_handle(T &&value)
{
return make_handle_in_place<Handle, typename std::decay<T>::type>(
std::forward<T>(value));
}
/**
* @brief Recover the boxed object from a handle ctx (NULL-safe).
*/
template <typename T> T *handle_impl(void *ctx)
{
auto *box = static_cast<RefCounted<T> *>(ctx);
return box ? &box->impl : nullptr;
}
/**
* @brief Shared free() body: release the ctx, no-op on NULL/empty handle.
*/
template <typename Handle> void free_handle(Handle *h)
{
if (!h || !h->ctx || !h->release)
return;
h->release(h->ctx);
h->ctx = nullptr;
}
} // namespace oakcodec
#endif // OAKCODEC_C_API_REFCOUNTED_H
+81
View File
@@ -0,0 +1,81 @@
# Oak Video Editor - 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/>.
add_library(oakcodec SHARED
conformmanager.cpp
conformmanager.h
decoder.cpp
decoder.h
encoder.cpp
encoder.h
exportcodec.cpp
exportcodec.h
exportformat.cpp
exportformat.h
footagedescription.h
frame.cpp
frame.h
framemanager.cpp
framemanager.h
oiioframebridge.cpp
oiioframebridge.h
planarfiledevice.cpp
planarfiledevice.h
proxymanager.cpp
proxymanager.h
taskcallbacks.cpp
taskcallbacks.h
timecodemetadata.cpp
timecodemetadata.h
)
add_subdirectory(ffmpeg)
add_subdirectory(oiio)
# In a full-tree build the repo root is CMAKE_SOURCE_DIR; a standalone
# build (see src/codec/standalone) sets OAK_REPO_ROOT explicitly.
if(NOT DEFINED OAK_REPO_ROOT)
set(OAK_REPO_ROOT ${CMAKE_SOURCE_DIR})
endif()
target_include_directories(oakcodec PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}
${OAK_REPO_ROOT}/include
${OAK_REPO_ROOT}/core/include
${OAK_REPO_ROOT}/ffmpeg_bridge/include
${OAK_REPO_ROOT}/third_party/openfx/include
${OIIO_INCLUDE_DIRS}
${OCIO_INCLUDE_DIRS}
)
# 01 §1 rule 5: only the OAKCODEC_API-marked C functions are exported;
# codec-internal C++ classes (olive::Frame, FootageDescription, ...) must not
# leak into the global symbol namespace where they would interpose on
# same-named weak symbols inside oakcommon/oakrender.
target_compile_options(oakcodec PRIVATE
-fvisibility=hidden
-fvisibility-inlines-hidden
)
# oakcommon's C API implementation links these PUBLICly; oakcodec consumes
# the oakcommon C ABI (and olivecore's C++ wrappers) only.
target_link_libraries(oakcodec PUBLIC
oakcommon
oakrender
olivecore
ffmpeg_bridge
${OCIO_LIBRARIES}
${OIIO_LIBRARIES}
)
+144
View File
@@ -0,0 +1,144 @@
/*
* Oak Video Editor - Non-Linear Video Editor
* Copyright (C) 2025 Olive CE 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/>.
*/
#include "conformmanager.h"
#include <filesystem>
#include "common/filefunctions.h"
#include "taskcallbacks.h"
namespace olive
{
ConformManager *ConformManager::instance_ = nullptr;
namespace
{
/**
* @brief oakcommon C API wrapper for FileFunctions::get_unique_file_identifier
*/
std::string unique_file_identifier(const std::string &filename)
{
OakFileFunctions ff = oakcommon_filefunctions_init();
if (!ff.ctx) {
return std::string();
}
std::string result;
int size = oakcommon_filefunctions_get_unique_file_identifier(
ff, filename.c_str(), nullptr, 0);
if (size > 0) {
result.resize(size_t(size) - 1); // size includes the NUL
oakcommon_filefunctions_get_unique_file_identifier(
ff, filename.c_str(), result.data(), size);
}
oakcommon_filefunctions_free(&ff);
return result;
}
} // namespace
ConformManager::Conform ConformManager::get_conform_state(
const std::string &cache_path, const Decoder::CodecStream &stream,
const core::AudioParams &params, bool wait)
{
// Return existing conform if exists
std::vector<std::string> filenames =
get_conformed_filename(cache_path, stream, params);
if (all_conforms_exist(filenames)) {
return { k_conform_exists, filenames };
}
if (!oakcodec_task_submit_is_registered()) {
// Interim state (pre-M8): no task system, conform cannot be generated
return { k_conform_unavailable, std::vector<std::string>() };
}
// The task owns the ".working" temporary names and the rename to the
// final per-channel filenames on success (previously done in
// conform_task_finished); output_filename carries the first channel's
// final path and the task derives the siblings.
OakCodecTaskRequest req = {};
req.kind = OAKCODEC_TASK_CONFORM;
req.input_filename = stream.filename().c_str();
req.output_filename =
filenames.empty() ? nullptr : filenames.front().c_str();
req.stream_index = stream.stream();
req.sample_rate = params.sample_rate();
req.channel_layout = params.channel_layout();
req.sample_format = int(params.format());
// Interim simplification: submission is synchronous - we always wait
// for SubmitTask to return, regardless of `wait`.
int result = SubmitTask(req);
if (result < 0) {
return { k_conform_unavailable, std::vector<std::string>() };
}
if (all_conforms_exist(filenames)) {
return { k_conform_exists, filenames };
}
if (wait) {
// Synchronous wait already happened and the conform still does not
// exist: report the wait as failed.
return { k_conform_unavailable, std::vector<std::string>() };
}
return { k_conform_generating, std::vector<std::string>() };
}
std::vector<std::string>
ConformManager::get_conformed_filename(const std::string &cache_path,
const Decoder::CodecStream &stream,
const core::AudioParams &params)
{
std::vector<std::string> filenames(size_t(params.channel_count()));
const std::string base = unique_file_identifier(stream.filename()) + "-" +
std::to_string(stream.stream()) + "." +
std::to_string(params.sample_rate()) + "." +
std::to_string(int(params.format())) + "." +
std::to_string(params.channel_layout());
for (size_t i = 0; i < filenames.size(); i++) {
filenames[i] = (std::filesystem::path(cache_path) /
(base + "." + std::to_string(i) + ".pcm"))
.string();
}
return filenames;
}
bool ConformManager::all_conforms_exist(const std::vector<std::string> &filenames)
{
std::error_code ec;
for (const std::string &fn : filenames) {
if (!std::filesystem::exists(fn, ec)) {
return false;
}
}
return true;
}
} // namespace olive
+111
View File
@@ -0,0 +1,111 @@
/*
* Oak Video Editor - Non-Linear Video Editor
* Copyright (C) 2025 Olive CE 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 OAK_CONFORMMANAGER_H
#define OAK_CONFORMMANAGER_H
#include <string>
#include <vector>
#include "decoder.h"
#include "olive/core/render/audioparams.h"
namespace olive
{
/**
* @brief Manages audio conform (pcm cache) generation
*
* Qt-free interim state: actual conform work is delegated to the global
* task submit callback (include/codec/task.h). While no callback is
* registered (pre-M8), requests report k_conform_unavailable instead of
* starting background work.
*
* Behavior changes vs. the Qt version:
* - The `conform_ready` signal is gone; completion notification is the
* task system's / facade's business.
* - Submission is synchronous: get_conform_state() calls the submit
* callback inline and re-checks the filesystem afterwards. `wait`
* only controls whether a post-submit miss is reported as
* k_conform_unavailable (wait) or k_conform_generating (queued).
*/
class ConformManager {
public:
static void create_instance()
{
if (!instance_) {
instance_ = new ConformManager();
}
}
static void destroy_instance()
{
delete instance_;
instance_ = nullptr;
}
static ConformManager *instance()
{
return instance_;
}
enum ConformState {
k_conform_exists,
k_conform_generating,
k_conform_unavailable /**< No task callback registered / submit failed. */
};
struct Conform {
ConformState state;
std::vector<std::string> filenames;
};
/**
* @brief Get conform state, and start conforming if no conform exists
*
* Stateless and thread-safe. The decoder_id parameter of the Qt
* version was dropped: the task request addresses the source by
* filename/stream only.
*/
Conform get_conform_state(const std::string &cache_path,
const Decoder::CodecStream &stream,
const core::AudioParams &params, bool wait);
/**
* @brief Get the destination filenames of an audio stream conformed to
* a set of parameters (one per channel)
*
* Pure path computation: never touches the filesystem and never
* submits work.
*/
static std::vector<std::string>
get_conformed_filename(const std::string &cache_path,
const Decoder::CodecStream &stream,
const core::AudioParams &params);
private:
ConformManager() = default;
static ConformManager *instance_;
static bool all_conforms_exist(const std::vector<std::string> &filenames);
};
} // namespace olive
#endif // OAK_CONFORMMANAGER_H
+458
View File
@@ -0,0 +1,458 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
Modifications Copyright (C) 2025 mikesolar
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/>.
***/
#include "decoder.h"
#include <algorithm>
#include <chrono>
#include <cstdio>
#include "conformmanager.h"
#include "ffmpeg/ffmpegdecoder.h"
#include "oiio/oiiodecoder.h"
#include "planarfiledevice.h"
namespace olive
{
namespace
{
/**
* @brief NULL/empty-handle-safe check of an oakrender cancel atom
* (borrowed pointer, used at several retrieval entry points)
*/
bool cancel_atom_is_cancelled(const OakCancelAtom *cancelled)
{
if (!cancelled || !cancelled->ctx) {
return false;
}
int c = 0;
oakrender_cancelatom_is_cancelled(*cancelled, &c);
return c != 0;
}
} // namespace
const Rational Decoder::k_any_timecode = RATIONAL_MIN;
Decoder::Decoder()
: cached_texture_(nullptr)
{
update_last_accessed();
}
Decoder::~Decoder()
{
oakrender_display_texture_free(cached_texture_);
}
void Decoder::increment_access_time(int64_t t)
{
last_accessed_ += t;
}
bool Decoder::open(const CodecStream &stream)
{
std::lock_guard<std::mutex> locker(mutex_);
update_last_accessed();
if (stream_.is_valid()) {
// Decoder is already open. Return TRUE if the stream is the stream we have, or FALSE if not.
if (stream_ == stream) {
return true;
} else {
fprintf(stderr, "Tried to open a decoder that was already open with another stream\n");
return false;
}
} else {
// Stream was not open, try opening it now
if (!stream.is_valid()) {
// Cannot open null stream
fprintf(stderr, "Decoder attempted to open null stream\n");
return false;
}
if (!stream.exists()) {
// Cannot open file that doesn't exist
fprintf(stderr, "Decoder attempted to open file that doesn't exist\n");
return false;
}
// Set stream
stream_ = stream;
// Try open internal
if (open_internal()) {
return true;
} else {
// Unset stream
fprintf(stderr, "Failed to open %s stream %d\n",
stream_.filename().c_str(), stream_.stream());
close_internal();
stream_.reset();
return false;
}
}
}
OakRenderTexture *Decoder::retrieve_video(const RetrieveVideoParams &p)
{
std::lock_guard<std::mutex> locker(mutex_);
update_last_accessed();
if (!stream_.is_valid()) {
fprintf(stderr, "Can't retrieve video on a closed decoder\n");
return nullptr;
}
if (!supports_video()) {
fprintf(stderr, "Decoder doesn't support video\n");
return nullptr;
}
if (cancel_atom_is_cancelled(p.cancelled)) {
return nullptr;
}
if (cached_texture_ && cached_time_ == p.time &&
cached_divider_ == p.divider) {
// Hand the caller its own reference; the cache keeps its own
return oakrender_display_texture_retain(cached_texture_);
}
OakRenderTexture *texture = retrieve_video_internal(p);
oakrender_display_texture_free(cached_texture_);
cached_texture_ = texture ? oakrender_display_texture_retain(texture) :
nullptr;
cached_time_ = p.time;
cached_divider_ = p.divider;
return texture;
}
FramePtr Decoder::retrieve_video_frame(const RetrieveVideoParams &p)
{
std::lock_guard<std::mutex> locker(mutex_);
update_last_accessed();
if (!stream_.is_valid()) {
fprintf(stderr, "Can't retrieve video frame on a closed decoder\n");
return nullptr;
}
if (!supports_video()) {
fprintf(stderr, "Decoder doesn't support video\n");
return nullptr;
}
if (cancel_atom_is_cancelled(p.cancelled)) {
return nullptr;
}
return retrieve_video_frame_internal(p);
}
Decoder::RetrieveAudioStatus
Decoder::retrieve_audio(SampleBuffer &dest, const TimeRange &range,
const AudioParams &params,
const std::string &cache_path, OakLoopMode loop_mode,
RenderMode::Mode mode)
{
std::lock_guard<std::mutex> locker(mutex_);
update_last_accessed();
if (!stream_.is_valid()) {
fprintf(stderr, "Can't retrieve audio on a closed decoder\n");
return k_invalid;
}
if (!supports_audio()) {
fprintf(stderr, "Decoder doesn't support audio\n");
return k_invalid;
}
if (params.sample_rate() <= 0 || params.channel_count() <= 0) {
fprintf(stderr, "Invalid audio parameters, skipping audio retrieve\n");
return k_invalid;
}
// Get conform state from ConformManager
ConformManager::Conform conform =
ConformManager::instance()->get_conform_state(
cache_path, stream_, params, (mode == RenderMode::k_online));
if (conform.state == ConformManager::k_conform_generating) {
return k_waiting_for_conform;
}
// See if we got the conform
if (retrieve_audio_from_conform(dest, conform.filenames, range, loop_mode,
params)) {
return k_ok;
} else {
return k_unknown_error;
}
}
int64_t Decoder::get_last_accessed_time()
{
return last_accessed_;
}
void Decoder::close()
{
std::lock_guard<std::mutex> locker(mutex_);
update_last_accessed();
oakrender_display_texture_free(cached_texture_);
cached_texture_ = nullptr;
if (stream_.is_valid()) {
close_internal();
stream_.reset();
} else {
fprintf(stderr, "Tried to close a decoder that wasn't open\n");
}
}
bool Decoder::conform_audio(const std::vector<std::string> &output_filenames,
const AudioParams &params, OakCancelAtom *cancelled)
{
return conform_audio_internal(output_filenames, params, cancelled);
}
/*
* DECODER STATIC PUBLIC MEMBERS
*/
std::vector<DecoderPtr> Decoder::receive_list_of_all_decoders()
{
std::vector<DecoderPtr> decoders;
// The order in which these decoders are added is their priority when probing. Hence FFmpeg should usually be last,
// since it supports so many formats and we presumably want to override those formats with a more specific decoder.
decoders.push_back(std::make_shared<OIIODecoder>());
decoders.push_back(std::make_shared<FFmpegDecoder>());
return decoders;
}
DecoderPtr Decoder::create_from_id(const std::string &id)
{
if (id.empty()) {
return nullptr;
}
// Create list to iterate through
std::vector<DecoderPtr> decoder_list = receive_list_of_all_decoders();
for (DecoderPtr d : decoder_list) {
if (d->id() == id) {
return d;
}
}
return nullptr;
}
void Decoder::signal_processing_progress(int64_t ts, int64_t duration)
{
if (duration != FB_NOPTS_VALUE && duration != 0) {
if (index_progress_callback_) {
index_progress_callback_(static_cast<double>(ts) /
static_cast<double>(duration));
}
}
}
std::string
Decoder::transform_image_sequence_file_name(const std::string &filename,
const int64_t &number)
{
int digit_count = get_image_sequence_digit_count(filename);
std::filesystem::path file_path(filename);
// QFileInfo::completeBaseName(): filename up to the first '.'
std::string original_basename = file_path.filename().string();
std::string::size_type dot = original_basename.find('.');
if (dot != std::string::npos) {
original_basename.erase(dot);
}
std::string new_basename =
original_basename.substr(0, original_basename.size() - digit_count);
char number_buf[32];
snprintf(number_buf, sizeof(number_buf), "%0*lld", digit_count,
static_cast<long long>(number));
new_basename += number_buf;
std::string new_filename = file_path.filename().string();
std::string::size_type pos = 0;
while ((pos = new_filename.find(original_basename, pos)) !=
std::string::npos) {
new_filename.replace(pos, original_basename.size(), new_basename);
pos += new_basename.size();
}
return (file_path.parent_path() / new_filename).string();
}
int Decoder::get_image_sequence_digit_count(const std::string &filename)
{
// QFileInfo::completeBaseName(): filename up to the first '.'
std::string basename =
std::filesystem::path(filename).filename().string();
std::string::size_type dot = basename.find('.');
if (dot != std::string::npos) {
basename.erase(dot);
}
// See if basename contains a number at the end
int digit_count = 0;
for (int i = int(basename.size()) - 1; i >= 0; i--) {
if (basename[size_t(i)] >= '0' && basename[size_t(i)] <= '9') {
digit_count++;
} else {
break;
}
}
return digit_count;
}
int64_t Decoder::get_image_sequence_index(const std::string &filename)
{
int digit_count = get_image_sequence_digit_count(filename);
std::string original_basename =
std::filesystem::path(filename).filename().string();
std::string::size_type dot = original_basename.find('.');
if (dot != std::string::npos) {
original_basename.erase(dot);
}
std::string number_only =
original_basename.substr(original_basename.size() - digit_count);
return strtoll(number_only.c_str(), nullptr, 10);
}
OakRenderTexture *Decoder::retrieve_video_internal(const RetrieveVideoParams &p)
{
(void) p;
return nullptr;
}
FramePtr Decoder::retrieve_video_frame_internal(const RetrieveVideoParams &p)
{
(void) p;
return nullptr;
}
bool Decoder::conform_audio_internal(
const std::vector<std::string> &filenames, const AudioParams &params,
OakCancelAtom *cancelled)
{
(void) filenames;
(void) cancelled;
(void) params;
return false;
}
bool Decoder::retrieve_audio_from_conform(
SampleBuffer &sample_buffer,
const std::vector<std::string> &conform_filenames, TimeRange range,
OakLoopMode loop_mode, const AudioParams &input_params)
{
PlanarFileDevice input;
if (input.open(conform_filenames, PlanarFileDevice::k_read_only)) {
// Offset range by audio start offset
range -= get_audio_start_offset();
int64_t read_index = input_params.time_to_bytes(range.in()) /
input_params.channel_count();
int64_t write_index = 0;
const int64_t buffer_length_in_bytes =
sample_buffer.sample_count() *
input_params.bytes_per_sample_per_channel();
while (write_index < buffer_length_in_bytes) {
if (loop_mode == OAKCOMMON_LOOP_MODE_LOOP) {
while (read_index >= input.size()) {
read_index -= input.size();
}
while (read_index < 0) {
read_index += input.size();
}
}
int64_t write_count = 0;
if (read_index < 0) {
// Reading before 0, write silence here until audio data would actually start
write_count = std::min(-read_index, buffer_length_in_bytes);
sample_buffer.silence_bytes(write_index,
write_index + write_count);
} else if (read_index >= input.size()) {
// Reading after data length, write silence until the end of the buffer
write_count = buffer_length_in_bytes - write_index;
sample_buffer.silence_bytes(write_index,
write_index + write_count);
} else {
write_count = std::min(input.size() - read_index,
buffer_length_in_bytes - write_index);
input.seek(read_index);
input.read(reinterpret_cast<char **>(
sample_buffer.to_raw_ptrs().data()),
write_count, write_index);
}
read_index += write_count;
write_index += write_count;
}
input.close();
return true;
}
return false;
}
void Decoder::update_last_accessed()
{
last_accessed_ =
std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now().time_since_epoch())
.count();
}
}
+399
View File
@@ -0,0 +1,399 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
Modifications Copyright (C) 2025 mikesolar
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 OAK_DECODER_H
#define OAK_DECODER_H
#include <atomic>
#include <cstdint>
#include <filesystem>
#include <functional>
#include <memory>
#include <mutex>
#include <string>
#include <vector>
#include "common/loopmode.h"
#include "common/videoparams.h"
#include "footagedescription.h"
#include "frame.h"
#include "node/block.h"
#include "olive/core/render/audioparams.h"
#include "olive/core/render/pixelformat.h"
#include "olive/core/render/samplebuffer.h"
#include "olive/core/util/rational.h"
#include "olive/core/util/timerange.h"
#include "render/cancelatom.h"
#include "render/renderer.h"
namespace olive
{
using core::AudioParams;
using core::PixelFormat;
using core::Rational;
using core::SampleBuffer;
using core::TimeRange;
/**
* @brief Local replacement for render/rendermodes.h
*
* oakrender's C API has no render-mode counterpart. Values mirror
* engine/render/rendermodes.h (k_offline = 0, k_online = 1).
*/
class RenderMode {
public:
enum Mode { k_offline, k_online };
};
/**
* @brief "Don't force a color range" sentinel for
* Decoder::RetrieveVideoParams::force_range (the actual ranges are
* the OAKCOMMON_COLOR_RANGE_* values).
*/
inline constexpr int k_color_range_default = -1;
class Decoder;
using DecoderPtr = std::shared_ptr<Decoder>;
#define DECODER_DEFAULT_DESTRUCTOR(x) \
virtual ~x() override \
{ \
close_internal(); \
}
/**
* @brief A decoder's is the main class for bringing external media into Olive
*
* Its responsibilities are to serve as
* abstraction from codecs/decoders and provide complete frames. These frames can be video or audio data and are
* provided as Frame objects in shared pointers to alleviate the responsibility of memory handling.
*
* The main function in a decoder is Retrieve() which should return complete image/audio data. A decoder should
* alleviate all the complexities of codec compression from the rest of the application (i.e. a decoder should never
* return a partial frame or require other parts of the system to interface directly with the codec). Often this will
* necessitate pre-emptively caching, indexing, or even fully transcoding media before using it which can be implemented
* through the Analyze() function.
*
* A decoder does NOT perform any pixel/sample format conversion. Frames should pass through the PixelService
* to be utilized in the rest of the rendering pipeline.
*/
class Decoder {
public:
enum RetrieveState { k_ready, k_failed_to_open, k_index_unavailable };
Decoder();
virtual ~Decoder();
/**
* @brief Unique decoder ID
*/
virtual std::string id() const = 0;
virtual bool supports_video()
{
return false;
}
virtual bool supports_audio()
{
return false;
}
void increment_access_time(int64_t t);
class CodecStream {
public:
CodecStream()
: stream_(-1)
, block_(nullptr)
{
}
CodecStream(const std::string &filename, int stream,
const OakNodeBlock *block)
: filename_(filename)
, stream_(stream)
, block_(block)
{
}
bool is_valid() const
{
return !filename_.empty() && stream_ >= 0;
}
bool exists() const
{
std::error_code ec;
return std::filesystem::exists(filename_, ec);
}
void reset()
{
*this = CodecStream();
}
bool operator==(const CodecStream &rhs) const
{
return filename_ == rhs.filename_ && stream_ == rhs.stream_;
}
const std::string &filename() const
{
return filename_;
}
int stream() const
{
return stream_;
}
/**
* @brief Associated timeline block (opaque oaknode handle)
*
* Borrowed pointer: codec only stores/compares it, never
* dereferences, retains, or frees it.
*/
const OakNodeBlock *block() const
{
return block_;
}
private:
std::string filename_;
int stream_;
const OakNodeBlock *block_;
};
/**
* @brief Open stream for decoding
*
* This function is thread safe.
*
* Returns TRUE if stream could be opened successfully. Also returns TRUE if the decoder is
* already open and the stream == the stream provided. Returns FALSE if the stream couldn't
* be opened OR if already open and the stream is NOT the same.
*/
bool open(const CodecStream &stream);
static const Rational k_any_timecode;
struct RetrieveVideoParams {
OakRenderRenderer *renderer = nullptr;
Rational time;
int divider = 1;
PixelFormat maximum_format = PixelFormat::invalid;
OakCancelAtom *cancelled = nullptr;
int force_range = k_color_range_default;
int src_interlacing = OAKCOMMON_VIDEO_INTERLACE_NONE;
};
/**
* @brief Retrieves a video frame from footage
*
* This function will always return a valid frame unless a fatal error occurs (in such case,
* nullptr will return). If the timecode is before the start of the footage, this function should
* return the first frame. Likewise, if it is after the timecode, this function should return the
* last frame.
*
* This function is thread safe and can only run while the decoder is open. \see Open()
*
* The returned texture handle is owned by the caller and must be
* released with oakrender_display_texture_free().
*/
OakRenderTexture *retrieve_video(const RetrieveVideoParams &p);
/**
* @brief Retrieves a decoded video frame in CPU memory.
*
* Used by render-process isolation to decode media in the main process and pass packed pixel
* data to workers through shared memory.
*/
FramePtr retrieve_video_frame(const RetrieveVideoParams &p);
enum RetrieveAudioStatus {
k_invalid = -1,
k_ok,
k_waiting_for_conform,
k_unknown_error
};
/**
* @brief Retrieve audio data from footage
*
* This function will always return a sample buffer unless a fatal error occurs (in such case,
* nullptr will return). The SampleBuffer should always have enough audio for the range provided.
*
* This function is thread safe and can only run while the decoder is open. \see Open()
*/
RetrieveAudioStatus retrieve_audio(SampleBuffer &dest, const TimeRange &range,
const AudioParams &params,
const std::string &cache_path,
OakLoopMode loop_mode,
RenderMode::Mode mode);
/**
* @brief Determine the last time this decoder instance was used in any way
*/
int64_t get_last_accessed_time();
/**
* @brief Generate a Footage object from a file
*
* If this decoder is able to parse this file, it will return a valid FootagePtr. Otherwise, it
* will return nullptr.
*
* For sub-classes, this function should be effectively static. We can't do virtual static
* functions in C++, but it should hold and access no state during its run.
*
* This function is re-entrant.
*/
virtual FootageDescription probe(const std::string &filename,
OakCancelAtom *cancelled) const = 0;
/**
* @brief Closes media/deallocates memory
*
* This function is thread safe and can only run while the decoder is open. \see Open()
*/
void close();
/**
* @brief Conform audio stream
*/
bool conform_audio(const std::vector<std::string> &output_filenames,
const AudioParams &params,
OakCancelAtom *cancelled = nullptr);
/**
* @brief Create a Decoder instance using a Decoder ID
*
* @return
*
* A Decoder instance or nullptr if a Decoder with this ID does not exist
*/
static DecoderPtr create_from_id(const std::string &id);
static std::string
transform_image_sequence_file_name(const std::string &filename,
const int64_t &number);
static int get_image_sequence_digit_count(const std::string &filename);
static int64_t get_image_sequence_index(const std::string &filename);
static std::vector<DecoderPtr> receive_list_of_all_decoders();
/**
* @brief Set a callback receiving indexing progress (0-1)
*
* Replaces the former index_progress Qt signal.
*/
void set_index_progress_callback(std::function<void(double)> callback)
{
index_progress_callback_ = std::move(callback);
}
protected:
/**
* @brief Internal open function
*
* Sub-classes must override this function. Function will already be mutexed, so there is no need
* to worry about thread safety. Also many other sanity checks will be done before this, so
* sub-classes only need to worry about their own opening functions. It is guaranteed that the
* decoder is not open yet and that the footage stream was from that sub-classes probe function.
*
* Return TRUE if everything opened successfully and the decoder is ready to work. Otherwise,
* return FALSE. If this function returns false, Decoder will call close_internal to clean any
* memory allocated during OpenInternal.
*/
virtual bool open_internal() = 0;
/**
* @brief Internal close function
*
* Sub-classes must override this function. Function should be able to safely clear all allocated
* memory. It may be called even if Open() didn't complete or RetrieveVideo() was never called.
*/
virtual void close_internal() = 0;
/**
* @brief Internal frame retrieval function
*
* Sub-classes must override this function IF they support video. Function is already mutexed
* so sub-classes don't need to worry about thread safety.
*
* The returned texture handle is owned by the caller and must be
* released with oakrender_display_texture_free().
*/
virtual OakRenderTexture *
retrieve_video_internal(const RetrieveVideoParams &p);
virtual FramePtr retrieve_video_frame_internal(const RetrieveVideoParams &p);
virtual bool
conform_audio_internal(const std::vector<std::string> &filenames,
const AudioParams &params, OakCancelAtom *cancelled);
void signal_processing_progress(int64_t ts, int64_t duration);
/**
* @brief Return currently open stream
*
* This function is NOT thread safe and should therefore only be called by thread safe functions.
*/
const CodecStream &stream() const
{
return stream_;
}
virtual Rational get_audio_start_offset() const
{
return 0;
}
private:
void update_last_accessed();
bool retrieve_audio_from_conform(
SampleBuffer &sample_buffer,
const std::vector<std::string> &conform_filenames, TimeRange range,
OakLoopMode loop_mode, const AudioParams &params);
CodecStream stream_;
std::mutex mutex_;
std::atomic_int64_t last_accessed_;
OakRenderTexture *cached_texture_;
Rational cached_time_;
int cached_divider_ = 0;
std::function<void(double)> index_progress_callback_;
};
}
#endif // OAK_DECODER_H
+778
View File
@@ -0,0 +1,778 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
Modifications Copyright (C) 2025 mikesolar
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/>.
***/
#include "encoder.h"
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <filesystem>
#include "common/filefunctions.h"
#include "olive/core/util/timecodefunctions.h"
#include "ffmpeg/ffmpegencoder.h"
#include "oiio/oiioencoder.h"
namespace olive
{
const std::regex Encoder::k_image_sequence_contains_digits("\\[[#]+\\]");
const std::regex Encoder::k_image_sequence_remove_digits(
"[\\-\\.\\ \\_]?\\[[#]+\\]");
namespace
{
int str_to_int(const std::string &s)
{
return int(std::strtol(s.c_str(), nullptr, 10));
}
int64_t str_to_int64(const std::string &s)
{
return std::strtoll(s.c_str(), nullptr, 10);
}
Rational video_params_pixel_aspect_ratio(OakVideoParams vp)
{
int n = 0, d = 1;
oakcommon_videoparams_get_pixel_aspect_ratio(vp, &n, &d);
return Rational(n, d);
}
Rational video_params_frame_rate_as_time_base(OakVideoParams vp)
{
int n = 0, d = 1;
oakcommon_videoparams_frame_rate_as_time_base(vp, &n, &d);
return Rational(n, d);
}
std::string filefunctions_get_configuration_location()
{
OakFileFunctions ff = oakcommon_filefunctions_init();
std::string result;
if (ff.ctx) {
int size =
oakcommon_filefunctions_get_configuration_location(ff, nullptr, 0);
if (size > 0) {
result.assign(size_t(size) - 1, '\0');
oakcommon_filefunctions_get_configuration_location(ff, result.data(),
size);
}
}
oakcommon_filefunctions_free(&ff);
return result;
}
} // namespace
Encoder::Encoder(const EncodingParams &params) : params_(params) {}
const EncodingParams &Encoder::params() const
{
return params_;
}
std::string Encoder::get_filename_for_frame(const Rational &frame)
{
if (params().video_is_image_sequence()) {
// Transform!
int64_t frame_index = core::Timecode::time_to_timestamp(
frame, video_params_frame_rate_as_time_base(
params().video_params()));
int digits =
get_image_sequence_placeholder_digit_count(params().filename());
char frame_index_str[32];
snprintf(frame_index_str, sizeof(frame_index_str), "%0*lld", digits,
static_cast<long long>(frame_index));
return std::regex_replace(params_.filename(),
k_image_sequence_contains_digits,
frame_index_str);
} else {
// Keep filename
return params_.filename();
}
}
int Encoder::get_image_sequence_placeholder_digit_count(
const std::string &filename)
{
std::smatch match;
int digit_count = 0;
if (std::regex_search(filename, match, k_image_sequence_contains_digits)) {
size_t start = size_t(match.position(0));
for (size_t i = start + 1; i < filename.size(); i++) {
if (filename.at(i) == '#') {
digit_count++;
} else {
break;
}
}
}
return digit_count;
}
bool Encoder::filename_contains_digit_placeholder(const std::string &filename)
{
return std::regex_search(filename, k_image_sequence_contains_digits);
}
std::string Encoder::filename_remove_digit_placeholder(std::string filename)
{
return std::regex_replace(filename, k_image_sequence_remove_digits, "");
}
EncodingParams::EncodingParams()
: video_enabled_(false)
, video_params_(oakcommon_videoparams_init())
, video_bit_rate_(0)
, video_min_bit_rate_(0)
, video_max_bit_rate_(0)
, video_buffer_size_(0)
, video_threads_(0)
, video_is_image_sequence_(false)
, color_transform_(oakcommon_colortransform_init_output(""))
, audio_enabled_(false)
, audio_bit_rate_(0)
, subtitles_enabled_(false)
, subtitles_are_sidecar_(false)
, video_scaling_method_(k_stretch)
, has_custom_range_(false)
{
}
EncodingParams::EncodingParams(const EncodingParams &other)
: filename_(other.filename_)
, format_(other.format_)
, video_enabled_(other.video_enabled_)
, video_codec_(other.video_codec_)
, video_params_(other.video_params_)
, video_opts_(other.video_opts_)
, video_bit_rate_(other.video_bit_rate_)
, video_min_bit_rate_(other.video_min_bit_rate_)
, video_max_bit_rate_(other.video_max_bit_rate_)
, video_buffer_size_(other.video_buffer_size_)
, video_threads_(other.video_threads_)
, video_pix_fmt_(other.video_pix_fmt_)
, video_is_image_sequence_(other.video_is_image_sequence_)
, color_transform_(other.color_transform_)
, audio_enabled_(other.audio_enabled_)
, audio_codec_(other.audio_codec_)
, audio_params_(other.audio_params_)
, audio_bit_rate_(other.audio_bit_rate_)
, subtitles_enabled_(other.subtitles_enabled_)
, subtitles_are_sidecar_(other.subtitles_are_sidecar_)
, subtitle_sidecar_fmt_(other.subtitle_sidecar_fmt_)
, subtitles_codec_(other.subtitles_codec_)
, export_length_(other.export_length_)
, video_scaling_method_(other.video_scaling_method_)
, has_custom_range_(other.has_custom_range_)
, custom_range_(other.custom_range_)
{
if (video_params_.ctx && video_params_.addref) {
video_params_.addref(video_params_.ctx);
}
if (color_transform_.ctx && color_transform_.addref) {
color_transform_.addref(color_transform_.ctx);
}
}
EncodingParams &EncodingParams::operator=(const EncodingParams &other)
{
if (this != &other) {
// addref the incoming handles before releasing ours so that
// self-shared handles survive the release below
if (other.video_params_.ctx && other.video_params_.addref) {
other.video_params_.addref(other.video_params_.ctx);
}
if (other.color_transform_.ctx && other.color_transform_.addref) {
other.color_transform_.addref(other.color_transform_.ctx);
}
oakcommon_videoparams_free(&video_params_);
oakcommon_colortransform_free(&color_transform_);
filename_ = other.filename_;
format_ = other.format_;
video_enabled_ = other.video_enabled_;
video_codec_ = other.video_codec_;
video_params_ = other.video_params_;
video_opts_ = other.video_opts_;
video_bit_rate_ = other.video_bit_rate_;
video_min_bit_rate_ = other.video_min_bit_rate_;
video_max_bit_rate_ = other.video_max_bit_rate_;
video_buffer_size_ = other.video_buffer_size_;
video_threads_ = other.video_threads_;
video_pix_fmt_ = other.video_pix_fmt_;
video_is_image_sequence_ = other.video_is_image_sequence_;
color_transform_ = other.color_transform_;
audio_enabled_ = other.audio_enabled_;
audio_codec_ = other.audio_codec_;
audio_params_ = other.audio_params_;
audio_bit_rate_ = other.audio_bit_rate_;
subtitles_enabled_ = other.subtitles_enabled_;
subtitles_are_sidecar_ = other.subtitles_are_sidecar_;
subtitle_sidecar_fmt_ = other.subtitle_sidecar_fmt_;
subtitles_codec_ = other.subtitles_codec_;
export_length_ = other.export_length_;
video_scaling_method_ = other.video_scaling_method_;
has_custom_range_ = other.has_custom_range_;
custom_range_ = other.custom_range_;
}
return *this;
}
EncodingParams::~EncodingParams()
{
oakcommon_videoparams_free(&video_params_);
oakcommon_colortransform_free(&color_transform_);
}
std::string EncodingParams::get_preset_path()
{
return (std::filesystem::path(filefunctions_get_configuration_location()) /
"exportpresets")
.string();
}
std::vector<std::string> EncodingParams::get_list_of_presets()
{
std::vector<std::string> list;
std::error_code ec;
for (const auto &entry : std::filesystem::directory_iterator(
get_preset_path(), ec)) {
if (entry.is_regular_file()) {
list.push_back(entry.path().filename().string());
}
}
// QDir::entryList(QDir::Files) sorted by name by default
std::sort(list.begin(), list.end());
return list;
}
void EncodingParams::enable_video(const OakVideoParams &video_params,
const ExportCodec::Codec &vcodec)
{
if (video_params.ctx && video_params.addref) {
video_params.addref(video_params.ctx);
}
oakcommon_videoparams_free(&video_params_);
video_params_ = video_params;
video_enabled_ = true;
video_codec_ = vcodec;
}
void EncodingParams::set_color_transform(
const OakColorTransform &color_transform)
{
if (color_transform.ctx && color_transform.addref) {
color_transform.addref(color_transform.ctx);
}
oakcommon_colortransform_free(&color_transform_);
color_transform_ = color_transform;
}
void EncodingParams::enable_audio(const AudioParams &audio_params,
const ExportCodec::Codec &acodec)
{
audio_enabled_ = true;
audio_params_ = audio_params;
audio_codec_ = acodec;
}
void EncodingParams::enable_subtitles(const ExportCodec::Codec &scodec)
{
subtitles_enabled_ = true;
subtitles_codec_ = scodec;
}
void EncodingParams::enable_sidecar_subtitles(const ExportFormat::Format &sfmt,
const ExportCodec::Codec &scodec)
{
subtitles_enabled_ = true;
subtitles_are_sidecar_ = true;
subtitle_sidecar_fmt_ = sfmt;
subtitles_codec_ = scodec;
}
void EncodingParams::disable_video()
{
video_enabled_ = false;
}
void EncodingParams::disable_audio()
{
audio_enabled_ = false;
}
void EncodingParams::disable_subtitles()
{
subtitles_enabled_ = false;
}
bool EncodingParams::load(XmlStreamReader *reader)
{
while (xml_read_next_start_element(reader)) {
if (reader->name() == "export") {
int version = 0;
for (const auto &attr : reader->attributes()) {
if (attr.name == "version") {
version = str_to_int(attr.value);
}
}
switch (version) {
case 1:
return load_v1(reader);
}
} else {
reader->skip_current_element();
}
}
return false;
}
bool EncodingParams::load(const std::string &xml)
{
XmlStreamReader reader(xml);
return load(&reader);
}
std::string EncodingParams::save_to_string() const
{
XmlStreamWriter writer;
save(&writer);
return writer.output();
}
void EncodingParams::save(XmlStreamWriter *writer) const
{
writer->write_start_element("export");
writer->write_attribute("version", std::to_string(k_encoder_params_version));
writer->write_text_element("filename", filename_);
writer->write_text_element("format", std::to_string(format_));
writer->write_text_element("range", std::to_string(has_custom_range_));
writer->write_text_element("customrangein", custom_range_.in().to_string());
writer->write_text_element("customrangeout",
custom_range_.out().to_string());
writer->write_start_element("video");
writer->write_attribute("enabled", std::to_string(video_enabled_));
if (video_enabled_) {
int vp_width = 0, vp_height = 0, vp_format = -1, vp_divider = 1;
oakcommon_videoparams_get_width(video_params_, &vp_width);
oakcommon_videoparams_get_height(video_params_, &vp_height);
oakcommon_videoparams_get_format(video_params_, &vp_format);
oakcommon_videoparams_get_divider(video_params_, &vp_divider);
int vp_time_base_num = 0, vp_time_base_den = 1;
oakcommon_videoparams_get_time_base(video_params_, &vp_time_base_num,
&vp_time_base_den);
writer->write_text_element("codec", std::to_string(video_codec_));
writer->write_text_element("width", std::to_string(vp_width));
writer->write_text_element("height", std::to_string(vp_height));
writer->write_text_element("format", std::to_string(vp_format));
writer->write_text_element(
"pixelaspect",
video_params_pixel_aspect_ratio(video_params_).to_string());
writer->write_text_element(
"timebase",
Rational(vp_time_base_num, vp_time_base_den).to_string());
writer->write_text_element("divider", std::to_string(vp_divider));
writer->write_text_element("bitrate", std::to_string(video_bit_rate_));
writer->write_text_element("minbitrate",
std::to_string(video_min_bit_rate_));
writer->write_text_element("maxbitrate",
std::to_string(video_max_bit_rate_));
writer->write_text_element("bufsize",
std::to_string(video_buffer_size_));
writer->write_text_element("threads", std::to_string(video_threads_));
writer->write_text_element("pixfmt", video_pix_fmt_);
writer->write_text_element("imgseq",
std::to_string(video_is_image_sequence_));
std::string color_output;
int color_output_size = oakcommon_colortransform_get_output(
color_transform_, nullptr, 0);
if (color_output_size > 0) {
color_output.assign(size_t(color_output_size) - 1, '\0');
oakcommon_colortransform_get_output(
color_transform_, color_output.data(), color_output_size);
}
writer->write_start_element("color");
writer->write_text_element("output", color_output);
writer->write_end_element(); // colortransform
writer->write_text_element("vscale",
std::to_string(video_scaling_method_));
if (!video_opts_.empty()) {
writer->write_start_element("opts");
for (const auto &entry : video_opts_) {
writer->write_start_element("entry");
writer->write_text_element("key", entry.first);
writer->write_text_element("value", entry.second);
writer->write_end_element(); // entry
}
writer->write_end_element(); // opts
}
}
writer->write_end_element(); // video
writer->write_start_element("audio");
writer->write_attribute("enabled", std::to_string(audio_enabled_));
if (audio_enabled_) {
writer->write_text_element("codec", std::to_string(audio_codec_));
writer->write_text_element(
"samplerate", std::to_string(audio_params_.sample_rate()));
writer->write_text_element(
"channellayout", std::to_string(audio_params().channel_layout()));
writer->write_text_element("format",
audio_params_.format().to_string());
writer->write_text_element("bitrate", std::to_string(audio_bit_rate_));
}
writer->write_start_element("subtitles");
writer->write_attribute("enabled", std::to_string(subtitles_enabled_));
if (subtitles_enabled_) {
writer->write_text_element("sidecar",
std::to_string(subtitles_are_sidecar_));
writer->write_text_element("sidecarformat",
std::to_string(subtitle_sidecar_fmt_));
writer->write_text_element("codec", std::to_string(subtitles_codec_));
}
writer->write_end_element(); // subtitles
writer->write_end_element(); // audio
writer->write_end_element(); // export
writer->write_end_document();
}
Encoder *Encoder::create_from_id(Type id, const EncodingParams &params)
{
switch (id) {
case k_encoder_type_none:
break;
case k_encoder_type_f_fmpeg:
return new FFmpegEncoder(params);
case k_encoder_type_oiio:
return new OIIOEncoder(params);
}
return nullptr;
}
Encoder::Type Encoder::get_type_from_format(ExportFormat::Format f)
{
switch (f) {
case ExportFormat::k_format_d_nx_hd:
case ExportFormat::k_format_matroska:
case ExportFormat::k_format_quick_time:
case ExportFormat::k_format_mpe_g4_video:
case ExportFormat::k_format_mpe_g4_audio:
case ExportFormat::k_format_wav:
case ExportFormat::k_format_aiff:
case ExportFormat::k_format_m_p3:
case ExportFormat::k_format_flac:
case ExportFormat::k_format_ogg:
case ExportFormat::k_format_web_m:
case ExportFormat::k_format_srt:
return k_encoder_type_f_fmpeg;
case ExportFormat::k_format_open_exr:
case ExportFormat::k_format_png:
case ExportFormat::k_format_tiff:
return k_encoder_type_oiio;
case ExportFormat::k_format_count:
break;
}
return k_encoder_type_none;
}
Encoder *Encoder::create_from_format(ExportFormat::Format f,
const EncodingParams &params)
{
return create_from_id(get_type_from_format(f), params);
}
Encoder *Encoder::create_from_params(const EncodingParams &params)
{
return create_from_format(params.format(), params);
}
std::vector<std::string>
Encoder::get_pixel_formats_for_codec(ExportCodec::Codec c) const
{
return std::vector<std::string>();
}
std::vector<SampleFormat>
Encoder::get_sample_formats_for_codec(ExportCodec::Codec c) const
{
return std::vector<SampleFormat>();
}
std::array<float, 16>
EncodingParams::generate_matrix(EncodingParams::VideoScalingMethod method,
int source_width, int source_height,
int dest_width, int dest_height)
{
// Identity (former default-constructed QMatrix4x4), row-major
std::array<float, 16> preview_matrix = { 1, 0, 0, 0, //
0, 1, 0, 0, //
0, 0, 1, 0, //
0, 0, 0, 1 };
if (method == EncodingParams::k_stretch) {
return preview_matrix;
}
float export_ar =
static_cast<float>(dest_width) / static_cast<float>(dest_height);
float source_ar =
static_cast<float>(source_width) / static_cast<float>(source_height);
// qFuzzyCompare(export_ar, source_ar)
if (std::abs(export_ar - source_ar) * 100000.0f <=
std::min(std::abs(export_ar), std::abs(source_ar))) {
return preview_matrix;
}
if ((export_ar > source_ar) == (method == EncodingParams::k_fit)) {
// scale(source_ar / export_ar, 1)
preview_matrix[0] = source_ar / export_ar;
} else {
// scale(1, export_ar / source_ar)
preview_matrix[5] = export_ar / source_ar;
}
return preview_matrix;
}
bool EncodingParams::load_v1(XmlStreamReader *reader)
{
Rational custom_range_in, custom_range_out;
while (xml_read_next_start_element(reader)) {
if (reader->name() == "filename") {
filename_ = reader->read_element_text();
} else if (reader->name() == "format") {
format_ = static_cast<ExportFormat::Format>(
str_to_int(reader->read_element_text()));
} else if (reader->name() == "range") {
has_custom_range_ = str_to_int(reader->read_element_text());
} else if (reader->name() == "customrangein") {
custom_range_in =
Rational::from_string(reader->read_element_text());
} else if (reader->name() == "customrangeout") {
custom_range_out =
Rational::from_string(reader->read_element_text());
} else if (reader->name() == "video") {
for (const auto &attr : reader->attributes()) {
if (attr.name == "enabled") {
video_enabled_ = str_to_int(attr.value);
}
}
while (xml_read_next_start_element(reader)) {
if (reader->name() == "codec") {
video_codec_ = static_cast<ExportCodec::Codec>(
str_to_int(reader->read_element_text()));
} else if (reader->name() == "width") {
oakcommon_videoparams_set_width(
video_params_,
str_to_int(reader->read_element_text()));
} else if (reader->name() == "height") {
oakcommon_videoparams_set_height(
video_params_,
str_to_int(reader->read_element_text()));
} else if (reader->name() == "format") {
oakcommon_videoparams_set_format(
video_params_,
str_to_int(reader->read_element_text()));
} else if (reader->name() == "pixelaspect") {
Rational par =
Rational::from_string(reader->read_element_text());
oakcommon_videoparams_set_pixel_aspect_ratio(
video_params_, par.numerator(), par.denominator());
} else if (reader->name() == "timebase") {
Rational tb =
Rational::from_string(reader->read_element_text());
oakcommon_videoparams_set_time_base(
video_params_, tb.numerator(), tb.denominator());
} else if (reader->name() == "divider") {
oakcommon_videoparams_set_divider(
video_params_,
str_to_int(reader->read_element_text()));
} else if (reader->name() == "bitrate") {
video_bit_rate_ = str_to_int64(reader->read_element_text());
} else if (reader->name() == "minbitrate") {
video_min_bit_rate_ =
str_to_int64(reader->read_element_text());
} else if (reader->name() == "maxbitrate") {
video_max_bit_rate_ =
str_to_int64(reader->read_element_text());
} else if (reader->name() == "bufsize") {
video_buffer_size_ =
str_to_int64(reader->read_element_text());
} else if (reader->name() == "threads") {
video_threads_ = str_to_int(reader->read_element_text());
} else if (reader->name() == "pixfmt") {
video_pix_fmt_ = reader->read_element_text();
} else if (reader->name() == "imgseq") {
video_is_image_sequence_ =
str_to_int(reader->read_element_text());
} else if (reader->name() == "color") {
while (xml_read_next_start_element(reader)) {
if (reader->name() == "output") {
OakColorTransform ct =
oakcommon_colortransform_init_output(
reader->read_element_text().c_str());
oakcommon_colortransform_free(&color_transform_);
color_transform_ = ct;
} else {
reader->skip_current_element();
}
}
} else if (reader->name() == "vscale") {
video_scaling_method_ = static_cast<VideoScalingMethod>(
str_to_int(reader->read_element_text()));
} else if (reader->name() == "opts") {
while (xml_read_next_start_element(reader)) {
if (reader->name() == "entry") {
std::string key, value;
while (xml_read_next_start_element(reader)) {
if (reader->name() == "key") {
key = reader->read_element_text();
} else if (reader->name() == "value") {
value = reader->read_element_text();
} else {
reader->skip_current_element();
}
}
set_video_option(key, value);
} else {
reader->skip_current_element();
}
}
} else {
reader->skip_current_element();
}
}
// HACK: Resolve bug where I forgot to serialize pixel aspect ratio
if (video_params_pixel_aspect_ratio(video_params_).isNull()) {
oakcommon_videoparams_set_pixel_aspect_ratio(video_params_, 1,
1);
}
} else if (reader->name() == "audio") {
for (const auto &attr : reader->attributes()) {
if (attr.name == "enabled") {
audio_enabled_ = str_to_int(attr.value);
}
}
while (xml_read_next_start_element(reader)) {
if (reader->name() == "codec") {
audio_codec_ = static_cast<ExportCodec::Codec>(
str_to_int(reader->read_element_text()));
} else if (reader->name() == "samplerate") {
audio_params_.set_sample_rate(
str_to_int(reader->read_element_text()));
} else if (reader->name() == "channellayout") {
audio_params_.set_channel_layout(
uint64_t(str_to_int64(reader->read_element_text())));
} else if (reader->name() == "format") {
audio_params_.set_format(
SampleFormat::from_string(reader->read_element_text()));
} else if (reader->name() == "bitrate") {
audio_bit_rate_ = str_to_int64(reader->read_element_text());
} else {
reader->skip_current_element();
}
}
// HACK: Resolve bug where I forgot to serialize the audio bit rate
if (!audio_bit_rate_) {
audio_bit_rate_ = 320000;
}
} else if (reader->name() == "subtitles") {
for (const auto &attr : reader->attributes()) {
if (attr.name == "enabled") {
subtitles_enabled_ = str_to_int(attr.value);
}
}
while (xml_read_next_start_element(reader)) {
if (reader->name() == "sidecar") {
subtitles_are_sidecar_ =
str_to_int(reader->read_element_text());
} else if (reader->name() == "sidecarformat") {
subtitle_sidecar_fmt_ = static_cast<ExportFormat::Format>(
str_to_int(reader->read_element_text()));
} else if (reader->name() == "codec") {
subtitles_codec_ = static_cast<ExportCodec::Codec>(
str_to_int(reader->read_element_text()));
} else {
reader->skip_current_element();
}
}
} else {
reader->skip_current_element();
}
}
// NOTE: custom_range_in/custom_range_out are intentionally not applied to
// custom_range_ — this matches the original behavior (they were read but
// never assigned).
(void) custom_range_in;
(void) custom_range_out;
return true;
}
}
+436
View File
@@ -0,0 +1,436 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
Modifications Copyright (C) 2025 mikesolar
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 OAK_ENCODER_H
#define OAK_ENCODER_H
#include <array>
#include <map>
#include <memory>
#include <regex>
#include <string>
#include <vector>
#include "olive/core/render/audioparams.h"
#include "olive/core/render/pixelformat.h"
#include "olive/core/render/samplebuffer.h"
#include "olive/core/render/sampleformat.h"
#include "olive/core/util/rational.h"
#include "olive/core/util/timerange.h"
#include "common/colortransform.h"
#include "common/videoparams.h"
#include "exportcodec.h"
#include "exportformat.h"
#include "frame.h"
#include "xmlutils.h"
namespace olive
{
using core::AudioParams;
using core::PixelFormat;
using core::Rational;
using core::SampleBuffer;
using core::SampleFormat;
using core::TimeRange;
class Encoder;
using EncoderPtr = std::shared_ptr<Encoder>;
/**
* @brief Parameters for an export encode
*
* Holds OakVideoParams / OakColorTransform C handles directly (no adapter
* layer). Copy constructor/assignment addref the handles, destructor
* releases them, so EncodingParams remains safely copyable by value
* (Encoder::params_ stores a copy).
*/
class EncodingParams {
public:
enum VideoScalingMethod { k_fit, k_stretch, k_crop };
EncodingParams();
EncodingParams(const EncodingParams &other);
EncodingParams &operator=(const EncodingParams &other);
~EncodingParams();
static std::string get_preset_path();
static std::vector<std::string> get_list_of_presets();
bool is_valid() const
{
return video_enabled_ || audio_enabled_ || subtitles_enabled_;
}
void set_filename(const std::string &filename)
{
filename_ = filename;
}
/**
* @brief Enable video with the given parameter set
*
* addrefs @p video_params; the caller keeps ownership of its own
* reference.
*/
void enable_video(const OakVideoParams &video_params,
const ExportCodec::Codec &vcodec);
void enable_audio(const AudioParams &audio_params,
const ExportCodec::Codec &acodec);
void enable_subtitles(const ExportCodec::Codec &scodec);
void enable_sidecar_subtitles(const ExportFormat::Format &sfmt,
const ExportCodec::Codec &scodec);
void disable_video();
void disable_audio();
void disable_subtitles();
const ExportFormat::Format &format() const
{
return format_;
}
void set_format(const ExportFormat::Format &format)
{
format_ = format;
}
void set_video_option(const std::string &key, const std::string &value)
{
video_opts_[key] = value;
}
void set_video_bit_rate(const int64_t &rate)
{
video_bit_rate_ = rate;
}
void set_video_min_bit_rate(const int64_t &rate)
{
video_min_bit_rate_ = rate;
}
void set_video_max_bit_rate(const int64_t &rate)
{
video_max_bit_rate_ = rate;
}
void set_video_buffer_size(const int64_t &sz)
{
video_buffer_size_ = sz;
}
void set_video_threads(const int &threads)
{
video_threads_ = threads;
}
void set_video_pix_fmt(const std::string &s)
{
video_pix_fmt_ = s;
}
void set_video_is_image_sequence(bool s)
{
video_is_image_sequence_ = s;
}
/**
* @brief Set the export color transform
*
* addrefs @p color_transform and releases the previously held handle.
*/
void set_color_transform(const OakColorTransform &color_transform);
const std::string &filename() const
{
return filename_;
}
bool video_enabled() const
{
return video_enabled_;
}
const ExportCodec::Codec &video_codec() const
{
return video_codec_;
}
/**
* @brief Borrowed video parameter handle (valid while this object lives)
*/
const OakVideoParams &video_params() const
{
return video_params_;
}
const std::map<std::string, std::string> &video_opts() const
{
return video_opts_;
}
std::string video_option(const std::string &key) const
{
auto it = video_opts_.find(key);
return it != video_opts_.end() ? it->second : std::string();
}
bool has_video_opt(const std::string &key) const
{
return video_opts_.count(key) > 0;
}
const int64_t &video_bit_rate() const
{
return video_bit_rate_;
}
const int64_t &video_min_bit_rate() const
{
return video_min_bit_rate_;
}
const int64_t &video_max_bit_rate() const
{
return video_max_bit_rate_;
}
const int64_t &video_buffer_size() const
{
return video_buffer_size_;
}
const int &video_threads() const
{
return video_threads_;
}
const std::string &video_pix_fmt() const
{
return video_pix_fmt_;
}
bool video_is_image_sequence() const
{
return video_is_image_sequence_;
}
/**
* @brief Borrowed color transform handle (valid while this object lives)
*/
const OakColorTransform &color_transform() const
{
return color_transform_;
}
bool audio_enabled() const
{
return audio_enabled_;
}
const ExportCodec::Codec &audio_codec() const
{
return audio_codec_;
}
const AudioParams &audio_params() const
{
return audio_params_;
}
const int64_t &audio_bit_rate() const
{
return audio_bit_rate_;
}
void set_audio_bit_rate(const int64_t &b)
{
audio_bit_rate_ = b;
}
bool subtitles_enabled() const
{
return subtitles_enabled_;
}
bool subtitles_are_sidecar() const
{
return subtitles_are_sidecar_;
}
ExportFormat::Format subtitle_sidecar_fmt() const
{
return subtitle_sidecar_fmt_;
}
ExportCodec::Codec subtitles_codec() const
{
return subtitles_codec_;
}
const Rational &get_export_length() const
{
return export_length_;
}
void set_export_length(const Rational &export_length)
{
export_length_ = export_length;
}
bool load(const std::string &xml);
bool load(XmlStreamReader *reader);
std::string save_to_string() const;
void save(XmlStreamWriter *writer) const;
bool has_custom_range() const
{
return has_custom_range_;
}
const TimeRange &custom_range() const
{
return custom_range_;
}
void set_custom_range(const TimeRange &custom_range)
{
has_custom_range_ = true;
custom_range_ = custom_range;
}
const VideoScalingMethod &video_scaling_method() const
{
return video_scaling_method_;
}
void
set_video_scaling_method(const VideoScalingMethod &video_scaling_method)
{
video_scaling_method_ = video_scaling_method;
}
/**
* @brief Generate a scaling matrix for the given scaling method
*
* De-Qt note: formerly returned QMatrix4x4. Now returns 16 floats in
* row-major order (m[row * 4 + column], matching QMatrix4x4's
* operator()(row, column) layout). The result is always a diagonal
* matrix: identity for k_stretch (or aspect-equal sources), otherwise
* a uniform axis scale at (0,0) and (1,1).
*/
static std::array<float, 16>
generate_matrix(VideoScalingMethod method, int source_width,
int source_height, int dest_width, int dest_height);
private:
static const int k_encoder_params_version = 1;
bool load_v1(XmlStreamReader *reader);
std::string filename_;
ExportFormat::Format format_ = ExportFormat::k_format_count;
bool video_enabled_;
ExportCodec::Codec video_codec_ = ExportCodec::k_codec_count;
OakVideoParams video_params_;
std::map<std::string, std::string> video_opts_;
int64_t video_bit_rate_;
int64_t video_min_bit_rate_;
int64_t video_max_bit_rate_;
int64_t video_buffer_size_;
int video_threads_;
std::string video_pix_fmt_;
bool video_is_image_sequence_;
OakColorTransform color_transform_;
bool audio_enabled_;
ExportCodec::Codec audio_codec_ = ExportCodec::k_codec_count;
AudioParams audio_params_;
int64_t audio_bit_rate_;
bool subtitles_enabled_;
bool subtitles_are_sidecar_;
ExportFormat::Format subtitle_sidecar_fmt_ = ExportFormat::k_format_count;
ExportCodec::Codec subtitles_codec_ = ExportCodec::k_codec_count;
Rational export_length_;
VideoScalingMethod video_scaling_method_;
bool has_custom_range_;
TimeRange custom_range_;
};
class Encoder {
public:
Encoder(const EncodingParams &params);
virtual ~Encoder() = default;
enum Type { k_encoder_type_none = -1, k_encoder_type_f_fmpeg, k_encoder_type_oiio };
/**
* @brief Create a Encoder instance using a Encoder ID
*
* @return
*
* A Encoder instance or nullptr if a Decoder with this ID does not exist
*/
static Encoder *create_from_id(Type id, const EncodingParams &params);
static Type get_type_from_format(ExportFormat::Format f);
static Encoder *create_from_format(ExportFormat::Format f,
const EncodingParams &params);
static Encoder *create_from_params(const EncodingParams &params);
virtual std::vector<std::string>
get_pixel_formats_for_codec(ExportCodec::Codec c) const;
virtual std::vector<SampleFormat>
get_sample_formats_for_codec(ExportCodec::Codec c) const;
const EncodingParams &params() const;
virtual PixelFormat get_desired_pixel_format() const
{
return PixelFormat::invalid;
}
const std::string &get_error() const
{
return error_;
}
std::string get_filename_for_frame(const Rational &frame);
static int get_image_sequence_placeholder_digit_count(const std::string &filename);
static bool filename_contains_digit_placeholder(const std::string &filename);
static std::string filename_remove_digit_placeholder(std::string filename);
static const std::regex k_image_sequence_contains_digits;
static const std::regex k_image_sequence_remove_digits;
virtual bool open() = 0;
virtual bool write_frame(olive::FramePtr frame,
olive::core::Rational time) = 0;
virtual bool write_audio(const olive::SampleBuffer &audio) = 0;
/**
* @brief Write one subtitle entry
*
* De-Qt note: formerly took a `const SubtitleBlock *` (an oaknode C++
* type). Now takes the flattened text and in/out times in seconds;
* callers extract them from the subtitle block via the oaknode C API.
*/
virtual bool write_subtitle(const char *text, double in_seconds,
double out_seconds) = 0;
virtual void close() = 0;
protected:
void set_error(const std::string &err)
{
error_ = err;
}
private:
EncodingParams params_;
std::string error_;
};
}
#endif // OAK_ENCODER_H
+139
View File
@@ -0,0 +1,139 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
Modifications Copyright (C) 2025 mikesolar
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/>.
***/
#include "exportcodec.h"
extern "C" {
}
namespace olive
{
std::string ExportCodec::get_codec_name(ExportCodec::Codec c)
{
switch (c) {
case k_codec_d_nx_hd:
return "DNxHD";
case k_codec_h264:
return "H.264";
case k_codec_h264rgb:
return "H.264 RGB";
case k_codec_h265:
return "H.265";
case k_codec_open_exr:
return "OpenEXR";
case k_codec_png:
return "PNG";
case k_codec_pro_res:
return "ProRes";
case k_codec_cineform:
return "Cineform";
case k_codec_tiff:
return "TIFF";
case k_codec_m_p2:
return "MP2";
case k_codec_m_p3:
return "MP3";
case k_codec_aac:
return "AAC";
case k_codec_pcm:
return "PCM (Uncompressed)";
case k_codec_flac:
return "FLAC";
case k_codec_opus:
return "Opus";
case k_codec_vorbis:
return "Vorbis";
case k_codec_v_p9:
return "VP9";
case k_codec_a_v1:
return "AV1";
case k_codec_srt:
return "SubRip SRT";
case k_codec_count:
break;
}
return "Unknown";
}
bool ExportCodec::is_codec_a_still_image(ExportCodec::Codec c)
{
switch (c) {
case k_codec_d_nx_hd:
case k_codec_h264:
case k_codec_h264rgb:
case k_codec_h265:
case k_codec_pro_res:
case k_codec_cineform:
case k_codec_m_p2:
case k_codec_m_p3:
case k_codec_aac:
case k_codec_pcm:
case k_codec_vorbis:
case k_codec_opus:
case k_codec_flac:
case k_codec_v_p9:
case k_codec_a_v1:
case k_codec_srt:
return false;
case k_codec_open_exr:
case k_codec_png:
case k_codec_tiff:
return true;
case k_codec_count:
break;
}
return false;
}
bool ExportCodec::is_codec_lossless(Codec c)
{
switch (c) {
case k_codec_pcm:
case k_codec_flac:
return true;
case k_codec_d_nx_hd:
case k_codec_h264:
case k_codec_h264rgb:
case k_codec_h265:
case k_codec_pro_res:
case k_codec_cineform:
case k_codec_m_p2:
case k_codec_m_p3:
case k_codec_aac:
case k_codec_vorbis:
case k_codec_opus:
case k_codec_v_p9:
case k_codec_a_v1:
case k_codec_srt:
case k_codec_open_exr:
case k_codec_png:
case k_codec_tiff:
case k_codec_count:
break;
}
return false;
}
}
+66
View File
@@ -0,0 +1,66 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
Modifications Copyright (C) 2025 mikesolar
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 OAK_EXPORTCODEC_H
#define OAK_EXPORTCODEC_H
#include <string>
namespace olive
{
class ExportCodec {
public:
// Only append to this list (never insert) because indexes are used in serialized files
enum Codec {
k_codec_d_nx_hd,
k_codec_h264,
k_codec_h264rgb,
k_codec_h265,
k_codec_open_exr,
k_codec_png,
k_codec_pro_res,
k_codec_cineform,
k_codec_tiff,
k_codec_v_p9,
k_codec_m_p2,
k_codec_m_p3,
k_codec_aac,
k_codec_pcm,
k_codec_opus,
k_codec_vorbis,
k_codec_flac,
k_codec_srt,
k_codec_a_v1,
k_codec_count
};
static std::string get_codec_name(Codec c);
static bool is_codec_a_still_image(Codec c);
static bool is_codec_lossless(Codec c);
};
}
#endif // OAK_EXPORTCODEC_H
+249
View File
@@ -0,0 +1,249 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
Modifications Copyright (C) 2025 mikesolar
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/>.
***/
#include "exportformat.h"
#include "encoder.h"
namespace olive
{
std::string ExportFormat::get_name(olive::ExportFormat::Format f)
{
switch (f) {
case k_format_d_nx_hd:
return "DNxHD";
case k_format_matroska:
return "Matroska Video";
case k_format_mpe_g4_video:
return "MPEG-4 Video";
case k_format_mpe_g4_audio:
return "MPEG-4 Audio";
case k_format_open_exr:
return "OpenEXR";
case k_format_png:
return "PNG";
case k_format_tiff:
return "TIFF";
case k_format_quick_time:
return "QuickTime";
case k_format_wav:
return "Wave Audio";
case k_format_aiff:
return "AIFF";
case k_format_m_p3:
return "MP3";
case k_format_flac:
return "FLAC";
case k_format_ogg:
return "Ogg";
case k_format_web_m:
return "WebM";
case k_format_srt:
return "SubRip SRT";
case k_format_count:
break;
}
return "Unknown";
}
std::string ExportFormat::get_extension(ExportFormat::Format f)
{
switch (f) {
case k_format_d_nx_hd:
return "mxf";
case k_format_matroska:
return "mkv";
case k_format_mpe_g4_video:
return "mp4";
case k_format_mpe_g4_audio:
return "m4a";
case k_format_open_exr:
return "exr";
case k_format_png:
return "png";
case k_format_tiff:
return "tiff";
case k_format_quick_time:
return "mov";
case k_format_wav:
return "wav";
case k_format_aiff:
return "aiff";
case k_format_m_p3:
return "mp3";
case k_format_flac:
return "flac";
case k_format_ogg:
return "ogg";
case k_format_web_m:
return "webm";
case k_format_srt:
return "srt";
case k_format_count:
break;
}
return std::string();
}
std::vector<ExportCodec::Codec> ExportFormat::get_video_codecs(ExportFormat::Format f)
{
switch (f) {
case k_format_d_nx_hd:
return { ExportCodec::k_codec_d_nx_hd };
case k_format_matroska:
return { ExportCodec::k_codec_h264, ExportCodec::k_codec_h264rgb,
ExportCodec::k_codec_h265, ExportCodec::k_codec_v_p9 };
case k_format_mpe_g4_video:
return { ExportCodec::k_codec_h264, ExportCodec::k_codec_h264rgb,
ExportCodec::k_codec_h265 };
case k_format_open_exr:
return { ExportCodec::k_codec_open_exr };
case k_format_png:
return { ExportCodec::k_codec_png };
case k_format_tiff:
return { ExportCodec::k_codec_tiff };
case k_format_quick_time:
return { ExportCodec::k_codec_h264, ExportCodec::k_codec_h264rgb,
ExportCodec::k_codec_h265, ExportCodec::k_codec_pro_res,
ExportCodec::k_codec_cineform };
case k_format_web_m:
return { ExportCodec::k_codec_a_v1, ExportCodec::k_codec_v_p9 };
case k_format_ogg:
case k_format_wav:
case k_format_mpe_g4_audio:
case k_format_aiff:
case k_format_m_p3:
case k_format_flac:
case k_format_srt:
case k_format_count:
break;
}
return {};
}
std::vector<ExportCodec::Codec> ExportFormat::get_audio_codecs(ExportFormat::Format f)
{
switch (f) {
// Video/audio formats
case k_format_d_nx_hd:
return { ExportCodec::k_codec_pcm };
case k_format_matroska:
return { ExportCodec::k_codec_aac, ExportCodec::k_codec_m_p2,
ExportCodec::k_codec_m_p3, ExportCodec::k_codec_pcm,
ExportCodec::k_codec_vorbis, ExportCodec::k_codec_opus,
ExportCodec::k_codec_flac };
case k_format_mpe_g4_video:
case k_format_mpe_g4_audio:
return { ExportCodec::k_codec_aac, ExportCodec::k_codec_m_p2,
ExportCodec::k_codec_m_p3 };
case k_format_quick_time:
return { ExportCodec::k_codec_aac, ExportCodec::k_codec_m_p2,
ExportCodec::k_codec_m_p3, ExportCodec::k_codec_pcm };
case k_format_web_m:
return { ExportCodec::k_codec_opus, ExportCodec::k_codec_aac,
ExportCodec::k_codec_m_p2, ExportCodec::k_codec_m_p3,
ExportCodec::k_codec_pcm, ExportCodec::k_codec_vorbis };
// Audio only formats
case k_format_wav:
return { ExportCodec::k_codec_pcm };
case k_format_aiff:
return { ExportCodec::k_codec_pcm };
case k_format_m_p3:
return { ExportCodec::k_codec_m_p3 };
case k_format_flac:
return { ExportCodec::k_codec_flac };
case k_format_ogg:
return { ExportCodec::k_codec_opus, ExportCodec::k_codec_vorbis,
ExportCodec::k_codec_pcm };
// Video only formats
case k_format_open_exr:
case k_format_png:
case k_format_tiff:
case k_format_srt:
case k_format_count:
break;
}
return {};
}
std::vector<ExportCodec::Codec> ExportFormat::get_subtitle_codecs(Format f)
{
switch (f) {
case k_format_d_nx_hd:
case k_format_mpe_g4_video:
case k_format_mpe_g4_audio:
case k_format_open_exr:
case k_format_quick_time:
case k_format_png:
case k_format_tiff:
case k_format_wav:
case k_format_aiff:
case k_format_m_p3:
case k_format_flac:
case k_format_ogg:
case k_format_web_m:
case k_format_count:
break;
case k_format_matroska:
case k_format_srt:
return { ExportCodec::k_codec_srt };
}
return {};
}
std::vector<std::string> ExportFormat::get_pixel_formats_for_codec(ExportFormat::Format f,
ExportCodec::Codec c)
{
Encoder *e = Encoder::create_from_format(f, EncodingParams());
std::vector<std::string> list;
if (e) {
list = e->get_pixel_formats_for_codec(c);
delete e;
}
return list;
}
std::vector<core::SampleFormat>
ExportFormat::get_sample_formats_for_codec(Format format, ExportCodec::Codec c)
{
std::vector<core::SampleFormat> f;
Encoder *e = Encoder::create_from_format(format, EncodingParams());
if (e) {
f = e->get_sample_formats_for_codec(c);
delete e;
}
return f;
}
}
+75
View File
@@ -0,0 +1,75 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
Modifications Copyright (C) 2025 mikesolar
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 OAK_EXPORTFORMAT_H
#define OAK_EXPORTFORMAT_H
#include <string>
#include <vector>
#include "olive/core/render/sampleformat.h"
#include "exportcodec.h"
namespace olive
{
class ExportFormat {
public:
// Only append to this list (never insert) because indexes are used in serialized files
enum Format {
k_format_d_nx_hd,
k_format_matroska,
k_format_mpe_g4_video,
k_format_open_exr,
k_format_quick_time,
k_format_png,
k_format_tiff,
k_format_wav,
k_format_aiff,
k_format_m_p3,
k_format_flac,
k_format_ogg,
k_format_web_m,
k_format_srt,
k_format_mpe_g4_audio,
k_format_count
};
static std::string get_name(Format f);
static std::string get_extension(Format f);
static std::vector<ExportCodec::Codec>
get_video_codecs(ExportFormat::Format f);
static std::vector<ExportCodec::Codec>
get_audio_codecs(ExportFormat::Format f);
static std::vector<ExportCodec::Codec>
get_subtitle_codecs(ExportFormat::Format f);
static std::vector<std::string>
get_pixel_formats_for_codec(Format f, ExportCodec::Codec c);
static std::vector<core::SampleFormat>
get_sample_formats_for_codec(Format f, ExportCodec::Codec c);
};
}
#endif // OAK_EXPORTFORMAT_H
+21
View File
@@ -0,0 +1,21 @@
# Olive - Non-Linear Video Editor
# Copyright (C) 2022 Olive 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/>.
target_sources(oakcodec PRIVATE avframeptr.h ffmpegdecoder.cpp
ffmpegdecoder.h
ffmpegencoder.cpp
ffmpegencoder.h
)
+139
View File
@@ -0,0 +1,139 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
Modifications Copyright (C) 2025 mikesolar
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 OAK_CODEC_FFMPEG_AVFRAMEPTR_H
#define OAK_CODEC_FFMPEG_AVFRAMEPTR_H
#include <stdint.h>
#include <memory>
#include <ffmpeg_bridge/ffmpeg_bridge.h>
namespace olive
{
/**
* @brief C++ adapter around the ffmpeg_bridge frame handle
*
* Mirrors the AVFrame field access the codebase used to perform directly,
* but every operation goes through the pure C bridge API so the editor
* never touches FFmpeg itself. The underlying frame object always lives
* inside the bridge library.
*/
class AVFrame {
public:
AVFrame() :
handle_(fb_frame_alloc())
{
}
explicit AVFrame(FBFrame *handle) :
handle_(handle)
{
}
~AVFrame()
{
if (handle_) {
fb_frame_free(&handle_);
}
}
AVFrame(const AVFrame &) = delete;
AVFrame &operator=(const AVFrame &) = delete;
FBFrame *handle() const { return handle_; }
int width() const { return fb_frame_get_width(handle_); }
void set_width(int w) { fb_frame_set_width(handle_, w); }
int height() const { return fb_frame_get_height(handle_); }
void set_height(int h) { fb_frame_set_height(handle_, h); }
int format() const { return fb_frame_get_format(handle_); }
void set_format(int f) { fb_frame_set_format(handle_, f); }
int64_t pts() const { return fb_frame_get_pts(handle_); }
void set_pts(int64_t p) { fb_frame_set_pts(handle_, p); }
int64_t best_effort_timestamp() const
{
return fb_frame_get_best_effort_timestamp(handle_);
}
int nb_samples() const { return fb_frame_get_nb_samples(handle_); }
void set_nb_samples(int n) { fb_frame_set_nb_samples(handle_, n); }
int sample_rate() const { return fb_frame_get_sample_rate(handle_); }
void set_sample_rate(int r) { fb_frame_set_sample_rate(handle_, r); }
int color_range() const { return fb_frame_get_color_range(handle_); }
void set_color_range(int r) { fb_frame_set_color_range(handle_, r); }
int colorspace() const { return fb_frame_get_colorspace(handle_); }
void set_colorspace(int cs) { fb_frame_set_colorspace(handle_, cs); }
uint64_t channel_layout_mask() const
{
return fb_frame_get_channel_layout_mask(handle_);
}
void set_channel_layout_mask(uint64_t m)
{
fb_frame_set_channel_layout_mask(handle_, m);
}
bool is_hw() const { return fb_frame_is_hw(handle_) != 0; }
int hw_transfer_data(const AVFrame *src)
{
return fb_frame_hw_transfer_data(handle_, src->handle_);
}
int get_buffer(int align) { return fb_frame_get_buffer(handle_, align); }
int make_writable() { return fb_frame_make_writable(handle_); }
uint8_t *data(int plane) { return fb_frame_get_data(handle_, plane); }
const uint8_t *data(int plane) const
{
return fb_frame_get_data_const(handle_, plane);
}
void set_data(int plane, uint8_t *d)
{
fb_frame_set_data(handle_, plane, d);
}
int linesize(int plane) const
{
return fb_frame_get_linesize(handle_, plane);
}
void set_linesize(int plane, int l)
{
fb_frame_set_linesize(handle_, plane, l);
}
private:
FBFrame *handle_;
};
using AVFramePtr = std::shared_ptr<AVFrame>;
inline AVFramePtr create_av_frame_ptr(FBFrame *f)
{
return std::make_shared<AVFrame>(f);
}
inline AVFramePtr create_av_frame_ptr()
{
return std::make_shared<AVFrame>();
}
}
#endif // OAK_CODEC_FFMPEG_AVFRAMEPTR_H
File diff suppressed because it is too large Load Diff
+151
View File
@@ -0,0 +1,151 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
Modifications Copyright (C) 2025 mikesolar
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 OAK_FFMPEGDECODER_H
#define OAK_FFMPEGDECODER_H
#include <inttypes.h>
#include <list>
#include <string>
#include <vector>
#include <ffmpeg_bridge/ffmpeg_bridge.h>
#include "decoder.h"
#include "ffmpeg/avframeptr.h"
namespace olive
{
/**
* @brief A Decoder derivative that uses the ffmpeg_bridge library as an Olive decoder
*
* All media access goes through the pure C API of the ffmpeg_bridge shared
* library; this class never sees an FFmpeg structure or function.
*/
class FFmpegDecoder : public Decoder {
public:
// Constructor
FFmpegDecoder();
// Destructor
DECODER_DEFAULT_DESTRUCTOR(FFmpegDecoder)
virtual std::string id() const override;
virtual bool supports_video() override
{
return true;
}
virtual bool supports_audio() override
{
return true;
}
virtual FootageDescription probe(const std::string &filename,
OakCancelAtom *cancelled) const override;
protected:
virtual bool open_internal() override;
virtual OakRenderTexture *
retrieve_video_internal(const RetrieveVideoParams &p) override;
virtual FramePtr
retrieve_video_frame_internal(const RetrieveVideoParams &p) override;
virtual bool
conform_audio_internal(const std::vector<std::string> &filenames,
const AudioParams &params,
OakCancelAtom *cancelled) override;
virtual void close_internal() override;
virtual Rational get_audio_start_offset() const override;
private:
/**
* @brief Handle a bridge error code
*
* Uses the bridge API to retrieve a descriptive string for this error code and sends it to Error(). As such, this
* function also automatically closes the Decoder.
*
* @param error_code
*/
static std::string f_fmpeg_error(int error_code);
void free_scaler();
AVFramePtr transfer_hardware_frame(AVFramePtr f);
static PixelFormat get_native_pixel_format(int pix_fmt);
static int get_native_channel_count(int pix_fmt);
static bool is_pixel_format_glsl_compatible(int f);
AVFramePtr get_frame_from_cache(const int64_t &t) const;
void clear_frame_cache();
AVFramePtr pre_process_frame(AVFramePtr f, const RetrieveVideoParams &p);
OakRenderTexture *process_frame_into_texture(AVFramePtr f,
const RetrieveVideoParams &p,
const AVFramePtr original);
AVFramePtr retrieve_frame(const Rational &time, OakCancelAtom *cancelled);
void remove_first_frame();
static int maximum_queue_size();
FBScaler *scaler_;
int scaler_src_width_;
int scaler_src_height_;
int scaler_src_format_;
int scaler_dst_width_;
int scaler_dst_height_;
int scaler_dst_format_;
int scaler_colrange_;
int scaler_colspace_;
FBPacket *working_packet_;
int64_t second_ts_;
std::list<AVFramePtr> cached_frames_;
bool cache_at_zero_;
bool cache_at_eof_;
FBDecoder *instance_;
// Stream parameters cached on open (the stream object itself lives
// inside the bridge library)
Rational stream_time_base_;
int64_t stream_start_time_;
int64_t stream_duration_;
int64_t format_start_time_;
int input_sample_format_;
int input_sample_rate_;
uint64_t input_channel_layout_mask_;
};
}
#endif // OAK_FFMPEGDECODER_H
+547
View File
@@ -0,0 +1,547 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
Modifications Copyright (C) 2025 mikesolar
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/>.
***/
#include "ffmpegencoder.h"
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#include <vector>
#include "common/ffmpegutils.h"
#include "common/subtitleparams.h"
#include "common/videoparams.h"
namespace olive
{
namespace
{
std::string to_lower(const std::string &s)
{
std::string r = s;
std::transform(r.begin(), r.end(), r.begin(),
[](unsigned char c) { return char(std::tolower(c)); });
return r;
}
bool contains(const std::string &haystack, const std::string &needle)
{
return haystack.find(needle) != std::string::npos;
}
Rational video_params_pixel_aspect_ratio(OakVideoParams vp)
{
int n = 0, d = 1;
oakcommon_videoparams_get_pixel_aspect_ratio(vp, &n, &d);
return Rational(n, d);
}
Rational video_params_frame_rate_as_time_base(OakVideoParams vp)
{
int n = 0, d = 1;
oakcommon_videoparams_frame_rate_as_time_base(vp, &n, &d);
return Rational(n, d);
}
std::string colortransform_get_output(OakColorTransform ct)
{
std::string result;
int size = oakcommon_colortransform_get_output(ct, nullptr, 0);
if (size > 0) {
result.assign(size_t(size) - 1, '\0');
oakcommon_colortransform_get_output(ct, result.data(), size);
}
return result;
}
} // namespace
FFmpegEncoder::FFmpegEncoder(const EncodingParams &params)
: Encoder(params)
, encoder_(nullptr)
, open_(false)
{
}
bool FFmpegEncoder::get_color_tags_for_colorspace(const std::string &colorspace,
int *primaries, int *trc,
int *matrix)
{
const std::string name = to_lower(colorspace);
if (contains(name, "pq") || contains(name, "2084")) {
*primaries = fb_color_primaries_bt2020;
*trc = fb_color_trc_pq;
*matrix = fb_col_spc_b_t2020_ncl;
return true;
}
if (contains(name, "hlg")) {
*primaries = fb_color_primaries_bt2020;
*trc = fb_color_trc_hlg;
*matrix = fb_col_spc_b_t2020_ncl;
return true;
}
if (contains(name, "2020")) {
*primaries = fb_color_primaries_bt2020;
*trc = fb_color_trc_bt709;
*matrix = fb_col_spc_b_t2020_ncl;
return true;
}
if (contains(name, "p3")) {
*primaries = fb_color_primaries_smpte432;
*trc = fb_color_trc_srgb;
*matrix = fb_col_spc_b_t709;
return true;
}
if (contains(name, "srgb")) {
*primaries = fb_color_primaries_bt709;
*trc = fb_color_trc_srgb;
*matrix = fb_col_spc_b_t709;
return true;
}
if (contains(name, "pal")) {
*primaries = fb_color_primaries_bt470bg;
*trc = fb_color_trc_gamma28;
*matrix = fb_col_spc_b_t470_bg;
return true;
}
if (contains(name, "ntsc")) {
*primaries = fb_color_primaries_smpte170m;
*trc = fb_color_trc_smpte170m;
*matrix = fb_col_spc_smpt_e170_m;
return true;
}
if (contains(name, "1886") || contains(name, "709")) {
*primaries = fb_color_primaries_bt709;
*trc = fb_color_trc_bt709;
*matrix = fb_col_spc_b_t709;
return true;
}
return false;
}
std::vector<std::string>
FFmpegEncoder::get_pixel_formats_for_codec(ExportCodec::Codec c) const
{
std::vector<std::string> pix_fmts;
int bridge_codec = export_codec_to_bridge(c);
if (bridge_codec != fb_codec_none) {
int count =
fb_encoder_codec_get_pixel_formats(bridge_codec, nullptr, 0);
if (count > 0) {
std::vector<const char *> names(static_cast<size_t>(count));
fb_encoder_codec_get_pixel_formats(bridge_codec, names.data(),
count);
for (int i = 0; i < count; i++) {
pix_fmts.push_back(names[size_t(i)] ? names[size_t(i)] : "");
}
}
}
return pix_fmts;
}
std::vector<SampleFormat>
FFmpegEncoder::get_sample_formats_for_codec(ExportCodec::Codec c) const
{
std::vector<SampleFormat> f;
if (c == ExportCodec::k_codec_pcm) {
// FFmpeg lists these as separate codecs so we need custom functionality here
// We list signed 16 first because ExportDialog will always use the first element by default
// (because first element is the "default" in FFmpeg)
f = { SampleFormat::s16, SampleFormat::u8, SampleFormat::s32,
SampleFormat::s64, SampleFormat::f32, SampleFormat::f64 };
} else {
int bridge_codec = export_codec_to_bridge(c);
if (bridge_codec != fb_codec_none) {
int count =
fb_encoder_codec_get_sample_formats(bridge_codec, nullptr, 0);
if (count > 0) {
std::vector<int> fmts(static_cast<size_t>(count));
fb_encoder_codec_get_sample_formats(bridge_codec, fmts.data(),
count);
for (int fmt : fmts) {
int native = -1;
oakcommon_ffmpegutils_get_native_sample_format(fmt,
&native);
if (native != SampleFormat::invalid) {
f.push_back(
SampleFormat(static_cast<SampleFormat::Format>(
native)));
}
}
}
}
}
return f;
}
bool FFmpegEncoder::open()
{
if (open_) {
return true;
}
FBEncoderConfig config;
memset(&config, 0, sizeof(config));
config.filename = params().filename().c_str();
// Storage keeping C strings alive until fb_encoder_create deep-copies them
std::string pix_fmt_str;
std::string subtitle_header;
std::vector<std::string> opt_key_storage;
std::vector<std::string> opt_value_storage;
std::vector<const char *> opt_keys;
std::vector<const char *> opt_values;
// Set up video if it's enabled
if (params().video_enabled()) {
const OakVideoParams &vp = params().video_params();
config.video_enabled = 1;
config.video_codec = export_codec_to_bridge(params().video_codec());
oakcommon_videoparams_get_width(vp, &config.video_width);
oakcommon_videoparams_get_height(vp, &config.video_height);
Rational pixel_aspect = video_params_pixel_aspect_ratio(vp);
config.video_pixel_aspect_num = pixel_aspect.numerator();
config.video_pixel_aspect_den = pixel_aspect.denominator();
Rational time_base = video_params_frame_rate_as_time_base(vp);
config.video_time_base_num = time_base.numerator();
config.video_time_base_den = time_base.denominator();
int frame_rate_num = 0, frame_rate_den = 1;
oakcommon_videoparams_get_frame_rate(vp, &frame_rate_num,
&frame_rate_den);
config.video_frame_rate_num = frame_rate_num;
config.video_frame_rate_den = frame_rate_den;
pix_fmt_str = params().video_pix_fmt();
config.video_pix_fmt = pix_fmt_str.c_str();
// This is the format we will expect frames received in Write() to be in
int native_pixel_fmt = -1;
oakcommon_videoparams_get_format(vp, &native_pixel_fmt);
// This is the format we will need to convert the frame to for the bridge to understand it
int compatible_fmt = -1;
oakcommon_ffmpegutils_get_compatible_pixel_format(native_pixel_fmt,
&compatible_fmt);
video_conversion_fmt_ =
PixelFormat(static_cast<PixelFormat::Format>(compatible_fmt));
// These are the equivalent pixel formats as bridge pixel formats
int src_alpha_pix_fmt = fb_pix_fmt_none;
int src_noalpha_pix_fmt = fb_pix_fmt_none;
oakcommon_ffmpegutils_get_ffmpeg_pixel_format(
compatible_fmt, OAKCOMMON_RGBA_CHANNEL_COUNT, &src_alpha_pix_fmt);
oakcommon_ffmpegutils_get_ffmpeg_pixel_format(
compatible_fmt, OAKCOMMON_RGB_CHANNEL_COUNT, &src_noalpha_pix_fmt);
if (src_alpha_pix_fmt == fb_pix_fmt_none ||
src_noalpha_pix_fmt == fb_pix_fmt_none) {
set_error("Failed to find suitable pixel format for this buffer");
return false;
}
config.video_src_pix_fmt = src_alpha_pix_fmt;
int color_range = OAKCOMMON_COLOR_RANGE_LIMITED;
oakcommon_videoparams_get_color_range(vp, &color_range);
config.video_color_range = color_range == OAKCOMMON_COLOR_RANGE_FULL ?
fb_color_range_jpeg :
fb_color_range_mpeg;
int interlacing = OAKCOMMON_VIDEO_INTERLACE_NONE;
oakcommon_videoparams_get_interlacing(vp, &interlacing);
switch (interlacing) {
case OAKCOMMON_VIDEO_INTERLACED_TOP_FIRST:
config.video_field_order = fb_field_order_tt;
break;
case OAKCOMMON_VIDEO_INTERLACED_BOTTOM_FIRST:
config.video_field_order = fb_field_order_bb;
break;
default:
config.video_field_order = fb_field_order_progressive;
break;
}
config.video_bit_rate = params().video_bit_rate();
config.video_min_bit_rate = params().video_min_bit_rate();
config.video_max_bit_rate = params().video_max_bit_rate();
config.video_buffer_size = params().video_buffer_size();
config.video_threads = params().video_threads();
const std::string color_output =
colortransform_get_output(params().color_transform());
config.video_color_srgb =
contains(to_lower(color_output), "srgb") ? 1 : 0;
// Derive explicit nclc tags (HDR etc.) from the export colorspace;
// the bridge falls back to the legacy sRGB/Rec.709 logic when these
// are unspecified
int color_primaries = fb_color_primaries_unspec;
int color_trc = fb_color_trc_unspec;
int color_matrix = fb_col_spc_unspec;
get_color_tags_for_colorspace(color_output, &color_primaries,
&color_trc, &color_matrix);
config.video_color_primaries = color_primaries;
config.video_color_trc = color_trc;
config.video_colorspace = color_matrix;
// Custom options (skip Olive-internal keys)
for (const auto &opt : params().video_opts()) {
if (opt.first.compare(0, 4, "ove_") != 0) {
opt_key_storage.push_back(opt.first);
opt_value_storage.push_back(opt.second);
}
}
for (size_t i = 0; i < opt_key_storage.size(); i++) {
opt_keys.push_back(opt_key_storage[i].c_str());
opt_values.push_back(opt_value_storage[i].c_str());
}
config.video_opt_keys = opt_keys.data();
config.video_opt_values = opt_values.data();
config.video_opt_count = int(opt_keys.size());
}
// Set up audio if it's enabled
if (params().audio_enabled()) {
config.audio_enabled = 1;
config.audio_codec = export_codec_to_bridge(params().audio_codec());
config.audio_sample_rate = params().audio_params().sample_rate();
config.audio_channel_layout_mask =
params().audio_params().channel_layout();
int audio_sample_fmt = -1;
oakcommon_ffmpegutils_get_ffmpeg_sample_format(
static_cast<int>(params().audio_params().format()),
&audio_sample_fmt);
config.audio_sample_format = audio_sample_fmt;
config.audio_bit_rate = params().audio_bit_rate();
}
// Set up subtitles if they're enabled
if (params().subtitles_enabled()) {
config.subtitles_enabled = 1;
config.subtitle_codec = export_codec_to_bridge(params().subtitles_codec());
int header_size =
oakcommon_subtitleparams_generate_ass_header(nullptr, 0);
if (header_size > 0) {
subtitle_header.assign(size_t(header_size) - 1, '\0');
oakcommon_subtitleparams_generate_ass_header(
subtitle_header.data(), header_size);
}
config.subtitle_header =
reinterpret_cast<const uint8_t *>(subtitle_header.data());
config.subtitle_header_size = int(subtitle_header.size());
}
encoder_ = fb_encoder_create(&config);
if (!encoder_) {
set_error("Failed to create encoder");
return false;
}
if (fb_encoder_open(encoder_) != 0) {
set_error_from_bridge();
fb_encoder_free(&encoder_);
return false;
}
open_ = true;
return true;
}
bool FFmpegEncoder::write_frame(FramePtr frame, Rational time)
{
// The render worker pool finishes tickets without a result when no
// worker is available (or the worker crashed); a null frame must fail
// the encode cleanly instead of crashing the export task.
if (!frame) {
fprintf(stderr,
"FFmpegEncoder::write_frame called with null frame\n");
return false;
}
// We may need to convert this frame to a frame that the bridge will understand
if (frame->format() != static_cast<int>(video_conversion_fmt_)) {
frame = frame->convert(static_cast<int>(video_conversion_fmt_));
}
int src_pix_fmt = fb_pix_fmt_none;
oakcommon_ffmpegutils_get_ffmpeg_pixel_format(
frame->format(), frame->channel_count(), &src_pix_fmt);
int r = fb_encoder_write_video_frame(
encoder_, frame->width(), frame->height(), src_pix_fmt,
reinterpret_cast<const uint8_t *>(frame->data()),
frame->linesize_bytes(), time.to_double());
if (r != 0) {
set_error_from_bridge();
return false;
}
return true;
}
bool FFmpegEncoder::write_audio(const SampleBuffer &audio)
{
if (!audio.is_allocated()) {
return true;
}
const AudioParams &audio_params = audio.audio_params().is_valid() ?
audio.audio_params() :
params().audio_params();
std::vector<const uint8_t *> channel_data(
size_t(audio.audio_params().channel_count()));
for (size_t i = 0; i < channel_data.size(); i++) {
channel_data[i] =
reinterpret_cast<const uint8_t *>(audio.data(int(i)));
}
int sample_fmt = -1;
oakcommon_ffmpegutils_get_ffmpeg_sample_format(
static_cast<int>(audio.audio_params().format()), &sample_fmt);
int r = fb_encoder_write_audio(
encoder_, channel_data.data(),
audio.audio_params().channel_count(), sample_fmt,
audio_params.sample_rate(), int64_t(audio_params.channel_layout()),
int64_t(audio.sample_count()));
if (r != 0) {
set_error_from_bridge();
return false;
}
return true;
}
bool FFmpegEncoder::write_audio_data(const AudioParams &audio_params,
const uint8_t **data,
int input_sample_count)
{
int sample_fmt = -1;
oakcommon_ffmpegutils_get_ffmpeg_sample_format(
static_cast<int>(audio_params.format()), &sample_fmt);
int r = fb_encoder_write_audio(
encoder_, data, audio_params.channel_count(), sample_fmt,
audio_params.sample_rate(), int64_t(audio_params.channel_layout()),
input_sample_count);
if (r != 0) {
set_error_from_bridge();
return false;
}
return true;
}
bool FFmpegEncoder::write_subtitle(const char *text, double in_seconds,
double out_seconds)
{
int r = fb_encoder_write_subtitle(encoder_, text, in_seconds, out_seconds);
if (r != 0) {
set_error_from_bridge();
return false;
}
return true;
}
void FFmpegEncoder::close()
{
if (encoder_) {
// Flushes encoders, writes the trailer, and frees everything
fb_encoder_free(&encoder_);
}
open_ = false;
}
void FFmpegEncoder::set_error_from_bridge()
{
const char *err = fb_encoder_get_error(encoder_);
set_error(err ? err : "");
}
int FFmpegEncoder::export_codec_to_bridge(ExportCodec::Codec c)
{
switch (c) {
case ExportCodec::k_codec_h264:
return fb_codec_h264;
case ExportCodec::k_codec_h264rgb:
return fb_codec_h264_rgb;
case ExportCodec::k_codec_d_nx_hd:
return fb_codec_dnxhd;
case ExportCodec::k_codec_pro_res:
return fb_codec_prores;
case ExportCodec::k_codec_cineform:
return fb_codec_cineform;
case ExportCodec::k_codec_h265:
return fb_codec_h265;
case ExportCodec::k_codec_v_p9:
return fb_codec_v_p9;
case ExportCodec::k_codec_a_v1:
return fb_codec_a_v1;
case ExportCodec::k_codec_open_exr:
return fb_codec_openexr;
case ExportCodec::k_codec_png:
return fb_codec_png;
case ExportCodec::k_codec_tiff:
return fb_codec_tiff;
case ExportCodec::k_codec_m_p2:
return fb_codec_m_p2;
case ExportCodec::k_codec_m_p3:
return fb_codec_m_p3;
case ExportCodec::k_codec_aac:
return fb_codec_aac;
case ExportCodec::k_codec_pcm:
return fb_codec_pcm;
case ExportCodec::k_codec_flac:
return fb_codec_flac;
case ExportCodec::k_codec_opus:
return fb_codec_opus;
case ExportCodec::k_codec_vorbis:
return fb_codec_vorbis;
case ExportCodec::k_codec_srt:
return fb_codec_srt;
case ExportCodec::k_codec_count:
break;
}
return fb_codec_none;
}
}
+98
View File
@@ -0,0 +1,98 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
Modifications Copyright (C) 2025 mikesolar
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 OAK_FFMPEGENCODER_H
#define OAK_FFMPEGENCODER_H
#include <ffmpeg_bridge/ffmpeg_bridge.h>
#include "encoder.h"
namespace olive
{
/**
* @brief An Encoder derivative that uses the ffmpeg_bridge library for encoding
*
* All encoding work happens inside the ffmpeg_bridge shared library through
* its pure C API; this class only translates EncodingParams into a bridge
* configuration and forwards calls.
*/
class FFmpegEncoder : public Encoder {
public:
FFmpegEncoder(const EncodingParams &params);
virtual std::vector<std::string>
get_pixel_formats_for_codec(ExportCodec::Codec c) const override;
virtual std::vector<SampleFormat>
get_sample_formats_for_codec(ExportCodec::Codec c) const override;
virtual bool open() override;
virtual bool write_frame(olive::FramePtr frame,
olive::core::Rational time) override;
virtual bool write_audio(const olive::SampleBuffer &audio) override;
bool write_audio_data(const AudioParams &audio_params, const uint8_t **data,
int input_sample_count);
virtual bool write_subtitle(const char *text, double in_seconds,
double out_seconds) override;
virtual void close() override;
virtual PixelFormat get_desired_pixel_format() const override
{
return video_conversion_fmt_;
}
/**
* @brief Derives nclc color tags from an output colorspace name
*
* Extracted for testability. Returns true when the name maps to
* explicit tags (PQ/HLG/BT.2020, sRGB, P3, Rec.601, Rec.709); returns
* false for unknown names, in which case the bridge's legacy
* Rec.709/sRGB inference applies.
*/
static bool get_color_tags_for_colorspace(const std::string &colorspace,
int *primaries, int *trc,
int *matrix);
private:
/**
* @brief Copy the last error message from the bridge into the encoder error state
*/
void set_error_from_bridge();
static int export_codec_to_bridge(ExportCodec::Codec c);
FBEncoder *encoder_;
PixelFormat video_conversion_fmt_;
bool open_;
};
}
#endif // OAK_FFMPEGENCODER_H
+291
View File
@@ -0,0 +1,291 @@
/***
Oak Video Editor - 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 OAK_CODEC_FOOTAGEDESCRIPTION_H
#define OAK_CODEC_FOOTAGEDESCRIPTION_H
#include <cassert>
#include <string>
#include <vector>
#include "common/subtitleparams.h"
#include "common/videoparams.h"
#include "olive/core/render/audioparams.h"
#include "olive/core/util/rational.h"
namespace olive
{
using core::AudioParams;
using core::Rational;
/**
* @brief Codec-internal replacement for the former
* node/project/footage/footagedescription.h
*
* Value type describing the streams a Decoder::probe() found in a file.
* oaknode has no C API counterpart, so codec keeps its own copy. Video and
* subtitle streams are stored as oakcommon by-value handles; the class
* addrefs on insert/copy and releases on destruction. The original's
* Track::Type mapping (get_type_of_stream) and XML load/save (probe cache)
* belonged to the oaknode-facing side and are intentionally not reproduced
* here; consumers can use stream_is_video/audio/subtitle.
*/
class FootageDescription {
public:
FootageDescription(const std::string &decoder = std::string())
: decoder_(decoder)
, total_stream_count_(0)
, has_source_start_time_(false)
{
}
FootageDescription(const FootageDescription &other)
: decoder_(other.decoder_)
, video_streams_(other.video_streams_)
, audio_streams_(other.audio_streams_)
, subtitle_streams_(other.subtitle_streams_)
, total_stream_count_(other.total_stream_count_)
, source_start_time_(other.source_start_time_)
, source_start_time_source_(other.source_start_time_source_)
, has_source_start_time_(other.has_source_start_time_)
{
for (const OakVideoParams &h : video_streams_) {
if (h.ctx) {
h.addref(h.ctx);
}
}
for (const OakSubtitleParams &h : subtitle_streams_) {
if (h.ctx) {
h.addref(h.ctx);
}
}
}
FootageDescription &operator=(const FootageDescription &other)
{
if (this != &other) {
release_streams();
decoder_ = other.decoder_;
video_streams_ = other.video_streams_;
audio_streams_ = other.audio_streams_;
subtitle_streams_ = other.subtitle_streams_;
total_stream_count_ = other.total_stream_count_;
source_start_time_ = other.source_start_time_;
source_start_time_source_ = other.source_start_time_source_;
has_source_start_time_ = other.has_source_start_time_;
for (const OakVideoParams &h : video_streams_) {
if (h.ctx) {
h.addref(h.ctx);
}
}
for (const OakSubtitleParams &h : subtitle_streams_) {
if (h.ctx) {
h.addref(h.ctx);
}
}
}
return *this;
}
~FootageDescription()
{
release_streams();
}
bool is_valid() const
{
return !decoder_.empty() &&
(!video_streams_.empty() || !audio_streams_.empty() ||
!subtitle_streams_.empty());
}
const std::string &decoder() const
{
return decoder_;
}
void add_video_stream(const OakVideoParams &video_params)
{
assert(!has_stream_index(stream_index_of(video_params)));
if (video_params.ctx) {
video_params.addref(video_params.ctx);
}
video_streams_.push_back(video_params);
}
void add_audio_stream(const AudioParams &audio_params)
{
assert(!has_stream_index(audio_params.stream_index()));
audio_streams_.push_back(audio_params);
}
void add_subtitle_stream(const OakSubtitleParams &sub_params)
{
assert(!has_stream_index(stream_index_of(sub_params)));
if (sub_params.ctx) {
sub_params.addref(sub_params.ctx);
}
subtitle_streams_.push_back(sub_params);
}
bool stream_is_video(int index) const
{
for (const OakVideoParams &vp : video_streams_) {
if (stream_index_of(vp) == index) {
return true;
}
}
return false;
}
bool stream_is_audio(int index) const
{
for (const AudioParams &ap : audio_streams_) {
if (ap.stream_index() == index) {
return true;
}
}
return false;
}
bool stream_is_subtitle(int index) const
{
for (const OakSubtitleParams &sp : subtitle_streams_) {
if (stream_index_of(sp) == index) {
return true;
}
}
return false;
}
bool has_stream_index(int index) const
{
return stream_is_video(index) || stream_is_audio(index) ||
stream_is_subtitle(index);
}
int get_stream_count() const
{
return total_stream_count_;
}
void set_stream_count(int s)
{
total_stream_count_ = s;
}
void set_source_start_time(const Rational &time, const std::string &source)
{
source_start_time_ = time;
source_start_time_source_ = source;
has_source_start_time_ = true;
}
bool has_source_start_time() const
{
return has_source_start_time_;
}
const Rational &source_start_time() const
{
return source_start_time_;
}
const std::string &source_start_time_source() const
{
return source_start_time_source_;
}
const std::vector<OakVideoParams> &get_video_streams() const
{
return video_streams_;
}
const std::vector<AudioParams> &get_audio_streams() const
{
return audio_streams_;
}
std::vector<AudioParams> &get_audio_streams()
{
return audio_streams_;
}
const std::vector<OakSubtitleParams> &get_subtitle_streams() const
{
return subtitle_streams_;
}
private:
static int stream_index_of(const OakVideoParams &params)
{
int index = -1;
oakcommon_videoparams_get_stream_index(params, &index);
return index;
}
static int stream_index_of(const OakSubtitleParams &params)
{
int index = -1;
oakcommon_subtitleparams_get_stream_index(params, &index);
return index;
}
void release_streams()
{
for (OakVideoParams &h : video_streams_) {
if (h.ctx) {
h.release(h.ctx);
}
}
video_streams_.clear();
for (OakSubtitleParams &h : subtitle_streams_) {
if (h.ctx) {
h.release(h.ctx);
}
}
subtitle_streams_.clear();
}
std::string decoder_;
std::vector<OakVideoParams> video_streams_;
std::vector<AudioParams> audio_streams_;
std::vector<OakSubtitleParams> subtitle_streams_;
int total_stream_count_;
Rational source_start_time_;
std::string source_start_time_source_;
bool has_source_start_time_;
};
}
#endif // OAK_CODEC_FOOTAGEDESCRIPTION_H
+237
View File
@@ -0,0 +1,237 @@
/***
Oak Video Editor - 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/>.
***/
#include "frame.h"
#include <cstring>
#include <iostream>
#include <OpenImageIO/imagebuf.h>
#include "common/oiioutils.h"
#include "framemanager.h"
#include "oiioframebridge.h"
namespace olive
{
Frame::Frame()
: params_(oakcommon_videoparams_init())
, data_(nullptr)
, data_size_(0)
, timestamp_(0)
, linesize_(0)
, linesize_pixels_(0)
{
}
Frame::~Frame()
{
destroy();
if (params_.ctx && params_.release) {
params_.release(params_.ctx);
params_.ctx = nullptr;
}
}
FramePtr Frame::create()
{
return std::make_shared<Frame>();
}
OakVideoParams Frame::video_params() const
{
OakVideoParams copy = params_;
if (copy.ctx && copy.addref) {
copy.addref(copy.ctx);
}
return copy;
}
void Frame::set_video_params(const OakVideoParams &params)
{
if (params_.ctx && params_.release) {
params_.release(params_.ctx);
}
params_ = params;
if (params_.ctx && params_.addref) {
params_.addref(params_.ctx);
}
linesize_ = generate_linesize_bytes(width(), format(), channel_count());
int bpp = bytes_per_pixel();
linesize_pixels_ = bpp > 0 ? linesize_ / bpp : 0;
}
FramePtr Frame::interlace(FramePtr top, FramePtr bottom)
{
OakVideoParams top_params = top->video_params();
OakVideoParams bottom_params = bottom->video_params();
int equal = 0;
oakcommon_videoparams_equals(top_params, bottom_params, &equal);
oakcommon_videoparams_free(&bottom_params);
if (!equal) {
fprintf(stderr,
"Tried to interlace two frames that had incompatible parameters\n");
oakcommon_videoparams_free(&top_params);
return nullptr;
}
FramePtr interlaced = Frame::create();
interlaced->set_video_params(top_params);
oakcommon_videoparams_free(&top_params);
interlaced->allocate();
int linesize = interlaced->linesize_bytes();
for (int i = 0; i < interlaced->height(); i++) {
FramePtr which = (i % 2 == 0) ? top : bottom;
memcpy(interlaced->data() + i * linesize,
which->const_data() + i * linesize, linesize);
}
return interlaced;
}
int Frame::generate_linesize_bytes(int width, int format, int channel_count)
{
// Align to 32 bytes (not sure if this is necessary?)
int bytes_per_pixel = oakcommon_videoparams_static_get_bytes_per_pixel(
static_cast<OakPixelFormat>(format), channel_count);
return bytes_per_pixel * ((width + 31) & ~31);
}
core::Color Frame::get_pixel(int x, int y) const
{
if (!contains_pixel(x, y)) {
return core::Color();
}
int byte_offset = y * linesize_bytes() + x * bytes_per_pixel();
return core::Color(reinterpret_cast<const char *>(data_ + byte_offset),
core_format(), channel_count());
}
bool Frame::contains_pixel(int x, int y) const
{
return (is_allocated() && x >= 0 && x < width() && y >= 0 && y < height());
}
void Frame::set_pixel(int x, int y, const core::Color &c)
{
if (!contains_pixel(x, y)) {
return;
}
int byte_offset = y * linesize_bytes() + x * bytes_per_pixel();
c.to_data(reinterpret_cast<char *>(data_ + byte_offset), core_format(),
channel_count());
}
bool Frame::allocate()
{
// Assume this frame is intended to be a video frame
int is_valid = 0;
oakcommon_videoparams_get_is_valid(params_, &is_valid);
if (!is_valid) {
std::cerr << "Tried to allocate a frame with invalid parameters";
return false;
}
if (is_allocated()) {
// Already allocated
return true;
}
data_size_ = linesize_ * height();
data_ = FrameManager::allocate(data_size_);
return true;
}
void Frame::destroy()
{
if (is_allocated()) {
FrameManager::deallocate(data_size_, data_);
data_size_ = 0;
data_ = nullptr;
}
}
/**
* @brief OIIO base type for a native pixel format, via the oakcommon C ABI
*
* The OakOIIOUtils object is stateless; a process-lifetime handle is kept
* here to avoid re-boxing it on every conversion.
*/
static OIIO::TypeDesc::BASETYPE oiio_base_type_for_format(int format)
{
static OakOIIOUtils utils = oakcommon_oiioutils_init();
int base_type = 0; // OIIO::TypeDesc::UNKNOWN
oakcommon_oiioutils_get_oiio_base_type_from_format(utils, format,
&base_type);
return static_cast<OIIO::TypeDesc::BASETYPE>(base_type);
}
FramePtr Frame::convert(int format) const
{
// Create new params with destination format
OakVideoParams params = video_params();
oakcommon_videoparams_set_format(params, format);
// Create new frame
FramePtr converted = Frame::create();
converted->set_video_params(params);
oakcommon_videoparams_free(&params);
converted->set_timestamp(timestamp_);
converted->allocate();
// Do the conversion through OIIO for convenience
OIIO::ImageBuf src(OIIO::ImageSpec(width(), height(), channel_count(),
oiio_base_type_for_format(this->format())));
oiio_frame_to_buffer(const_data(), linesize_bytes(), &src);
OIIO::ImageBuf dst(OIIO::ImageSpec(converted->width(), converted->height(),
channel_count(),
oiio_base_type_for_format(format)));
if (dst.copy_pixels(src)) {
oiio_buffer_to_frame(&dst, converted->data(),
converted->linesize_bytes());
return converted;
} else {
return nullptr;
}
}
}
+210
View File
@@ -0,0 +1,210 @@
/***
Oak Video Editor - 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 OAK_FRAME_H
#define OAK_FRAME_H
#include <memory>
#include <olive/core/render/pixelformat.h>
#include <olive/core/util/color.h>
#include <olive/core/util/rational.h>
#include "common/videoparams.h"
namespace olive
{
class Frame;
using FramePtr = std::shared_ptr<Frame>;
/**
* @brief Video frame data or audio sample data from a Decoder
*
* Qt-free (M5). The parameter set is held as an OakVideoParams handle
* (oakcommon C ABI); every access goes through the oakcommon_videoparams_*
* functions. Pixel formats cross the boundary as OakPixelFormat int codes
* (numerically identical to olive::core::PixelFormat::Format).
*/
class Frame {
public:
Frame();
~Frame();
Frame(const Frame &) = delete;
static FramePtr create();
/**
* @brief Return a copy of the frame's parameter handle
*
* The returned handle has had its reference count incremented; the
* caller must release it (oakcommon_videoparams_free()).
*/
OakVideoParams video_params() const;
void set_video_params(const OakVideoParams &params);
static FramePtr interlace(FramePtr top, FramePtr bottom);
static int generate_linesize_bytes(int width, int format,
int channel_count);
int linesize_pixels() const
{
return linesize_pixels_;
}
int linesize_bytes() const
{
return linesize_;
}
int width() const
{
int width = 0;
oakcommon_videoparams_get_effective_width(params_, &width);
return width;
}
int height() const
{
int height = 0;
oakcommon_videoparams_get_effective_height(params_, &height);
return height;
}
/**
* @brief Pixel format as an OakPixelFormat value
* (== olive::core::PixelFormat::Format ordinal).
*/
int format() const
{
int format = OAKCOMMON_PIXEL_FORMAT_INVALID;
oakcommon_videoparams_get_format(params_, &format);
return format;
}
int channel_count() const
{
int count = 0;
oakcommon_videoparams_get_channel_count(params_, &count);
return count;
}
core::Color get_pixel(int x, int y) const;
bool contains_pixel(int x, int y) const;
void set_pixel(int x, int y, const core::Color &c);
/**
* @brief Get frame's timestamp.
*
* This timestamp is always a Rational that will equate to the time in seconds.
*/
const core::Rational &timestamp() const
{
return timestamp_;
}
void set_timestamp(const core::Rational &timestamp)
{
timestamp_ = timestamp;
}
/**
* @brief Get the data buffer of this frame
*/
char *data()
{
return data_;
}
/**
* @brief Get the const data buffer of this frame
*/
const char *const_data() const
{
return data_;
}
/**
* @brief Allocate memory buffer to store data based on parameters
*
* For video frames, the width(), height(), and format() must be set for this function to work.
*
* If a memory buffer has been previously allocated without destroying, this function will destroy it.
*/
bool allocate();
/**
* @brief Return whether the frame is allocated or not
*/
bool is_allocated() const
{
return data_;
}
/**
* @brief Destroy a memory buffer allocated with allocate()
*/
void destroy();
/**
* @brief Returns the size of the array returned in data() in bytes
*
* Returns 0 if nothing is allocated.
*/
int allocated_size() const
{
return data_size_;
}
FramePtr convert(int format) const;
private:
int bytes_per_pixel() const
{
int bytes = 0;
oakcommon_videoparams_get_bytes_per_pixel(params_, &bytes);
return bytes;
}
core::PixelFormat core_format() const
{
return core::PixelFormat(
static_cast<core::PixelFormat::Format>(format()));
}
OakVideoParams params_;
char *data_;
int data_size_;
core::Rational timestamp_;
int linesize_;
int linesize_pixels_;
};
}
#endif // OAK_FRAME_H
+22
View File
@@ -0,0 +1,22 @@
# Olive - Non-Linear Video Editor
# Copyright (C) 2022 Olive 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/>.
target_sources(oakcodec PRIVATE
oiiodecoder.cpp
oiiodecoder.h
oiioencoder.cpp
oiioencoder.h
)
+400
View File
@@ -0,0 +1,400 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
Modifications Copyright (C) 2025 mikesolar
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/>.
***/
#include "oiiodecoder.h"
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#include <filesystem>
#include "common/oiioutils.h"
namespace olive
{
std::vector<std::string> OIIODecoder::supported_formats;
namespace
{
// Thin wrappers over the oakcommon_oiioutils_* C API (replacing the former
// OIIOUtils C++ class). The handle is stateless, so it is created and
// released per call.
int pix_format_from_oiio_basetype(int base_type)
{
int out = OAKCOMMON_PIXEL_FORMAT_INVALID;
OakOIIOUtils utils = oakcommon_oiioutils_init();
oakcommon_oiioutils_get_format_from_oiio_basetype(utils, base_type, &out);
oakcommon_oiioutils_free(&utils);
return out;
}
int oiio_base_type_from_pix_format(int pixel_format)
{
int out = 0; // OIIO::TypeDesc::UNKNOWN
OakOIIOUtils utils = oakcommon_oiioutils_init();
oakcommon_oiioutils_get_oiio_base_type_from_format(utils, pixel_format,
&out);
oakcommon_oiioutils_free(&utils);
return out;
}
/**
* @brief Flatten an OakVideoParams handle into the oakrender POD
* (needed at every texture creation point)
*/
void fill_render_params(const OakVideoParams &params,
oakrender_video_params *out)
{
*out = oakrender_video_params{};
oakcommon_videoparams_get_width(params, &out->width);
oakcommon_videoparams_get_height(params, &out->height);
oakcommon_videoparams_get_time_base(params, &out->time_base_num,
&out->time_base_den);
oakcommon_videoparams_get_format(params, &out->format);
oakcommon_videoparams_get_pixel_aspect_ratio(
params, &out->pixel_aspect_num, &out->pixel_aspect_den);
oakcommon_videoparams_get_interlacing(params, &out->interlacing);
oakcommon_videoparams_get_color_range(params, &out->color_range);
oakcommon_videoparams_get_divider(params, &out->divider);
oakcommon_videoparams_get_video_type(params, &out->video_type);
oakcommon_videoparams_get_premultiplied_alpha(params,
&out->premultiplied_alpha);
}
std::vector<std::string> split_string(const std::string &s, char delimiter)
{
std::vector<std::string> out;
std::string::size_type start = 0;
while (true) {
std::string::size_type pos = s.find(delimiter, start);
if (pos == std::string::npos) {
out.push_back(s.substr(start));
break;
}
out.push_back(s.substr(start, pos - start));
start = pos + 1;
}
return out;
}
std::string to_lower(const std::string &s)
{
std::string out = s;
std::transform(out.begin(), out.end(), out.begin(),
[](unsigned char c) { return char(std::tolower(c)); });
return out;
}
} // namespace
OIIODecoder::OIIODecoder()
: image_(nullptr)
{
}
std::string OIIODecoder::id() const
{
return "oiio";
}
FootageDescription OIIODecoder::probe(const std::string &filename,
OakCancelAtom *cancelled) const
{
(void) cancelled;
FootageDescription desc(id());
// Filter out any file extensions that aren't expected to work - sometimes OIIO will crash trying
// to open a file that it can't if it's given one
if (!file_type_is_supported(filename)) {
return desc;
}
auto in = OIIO::ImageInput::open(filename);
if (!in) {
return desc;
}
// Filter out OIIO detecting an "FFmpeg movie", we have a native FFmpeg decoder that can handle
// it better
if (!strcmp(in->format_name(), "FFmpeg movie")) {
return desc;
}
bool stream_enabled = true;
int i;
for (i = 0; in->seek_subimage(i, 0); i++) {
OIIO::ImageSpec spec = in->spec();
OakVideoParams video_params = get_video_params_from_image_spec(spec);
oakcommon_videoparams_set_stream_index(video_params, i);
if (i > 1) {
// This is a multilayer image and this image might have an offset
OIIO::ImageSpec root_spec = in->spec(0);
float norm_x = spec.x + float(spec.width) * 0.5f -
float(root_spec.width) * 0.5f;
float norm_y = spec.y + float(spec.height) * 0.5f -
float(root_spec.height) * 0.5f;
oakcommon_videoparams_set_x(video_params, norm_x);
oakcommon_videoparams_set_y(video_params, norm_y);
}
// By default, only enable the first subimage (presumably the combined image). Later we will
// ask the user if they want to enable the layers instead.
oakcommon_videoparams_set_enabled(video_params, stream_enabled ? 1 : 0);
stream_enabled = false;
// OIIO automatically premultiplies alpha
// FIXME: We usually disassociate the alpha for the color management later, for 8-bit images this
// likely reduces the fidelity?
oakcommon_videoparams_set_premultiplied_alpha(video_params, 1);
desc.add_video_stream(video_params);
oakcommon_videoparams_free(&video_params);
}
desc.set_stream_count(i);
// If we're here, we have a successful image open
in->close();
return desc;
}
bool OIIODecoder::open_internal()
{
// If we can open the filename provided, assume everything is working
return open_image_handler(stream().filename(), stream().stream());
}
OakRenderTexture *
OIIODecoder::retrieve_video_internal(const RetrieveVideoParams &p)
{
FramePtr frame = retrieve_video_frame_internal(p);
if (!frame) {
return nullptr;
}
OakVideoParams frame_params = frame->video_params(); // addref'd copy
oakrender_video_params rvp;
fill_render_params(frame_params, &rvp);
oakcommon_videoparams_free(&frame_params);
// Frame linesize is already in bytes, which is what the C API expects
return oakrender_display_texture_create(p.renderer, &rvp, frame->data(),
frame->linesize_bytes());
}
FramePtr OIIODecoder::retrieve_video_frame_internal(const RetrieveVideoParams &p)
{
OakVideoParams vp = get_video_params_from_image_spec(image_->spec());
oakcommon_videoparams_set_divider(vp, p.divider);
if (!buffer_.is_allocated() || last_params_.divider != p.divider) {
last_params_ = p;
buffer_.destroy();
buffer_.set_video_params(vp); // Frame addrefs the handle
buffer_.allocate();
if (p.divider == 1) {
// Just upload straight to the buffer
image_->read_image(0, 0, 0, -1, oiio_pix_fmt_, buffer_.data());
} else {
OIIO::ImageBuf buf(image_->spec());
image_->read_image(0, 0, 0, -1, image_->spec().format,
buf.localpixels(), buf.pixel_stride(),
buf.scanline_stride(), buf.z_stride());
// Roughly downsample image for divider (for some reason OIIO::ImageBufAlgo::resample failed here)
int px_sz = 0;
oakcommon_videoparams_get_bytes_per_pixel(vp, &px_sz);
for (int dst_y = 0; dst_y < buffer_.height(); dst_y++) {
int src_y = dst_y * buf.spec().height / buffer_.height();
for (int dst_x = 0; dst_x < buffer_.width(); dst_x++) {
int src_x = dst_x * buf.spec().width / buffer_.width();
memcpy(buffer_.data() + buffer_.linesize_bytes() * dst_y +
px_sz * dst_x,
static_cast<uint8_t *>(buf.localpixels()) +
buf.scanline_stride() * src_y + px_sz * src_x,
px_sz);
}
}
}
}
int format = OAKCOMMON_PIXEL_FORMAT_INVALID;
oakcommon_videoparams_get_format(vp, &format);
oakcommon_videoparams_free(&vp);
// Force F32 output for all still images
if (format != PixelFormat::f32) {
FramePtr f32_frame = buffer_.convert(PixelFormat::f32);
if (f32_frame) {
f32_frame->set_timestamp(p.time);
return f32_frame;
}
}
FramePtr frame = Frame::create();
OakVideoParams buffer_params = buffer_.video_params(); // addref'd copy
frame->set_video_params(buffer_params);
oakcommon_videoparams_free(&buffer_params);
frame->set_timestamp(p.time);
if (!frame->allocate()) {
return nullptr;
}
memcpy(frame->data(), buffer_.const_data(),
size_t(buffer_.allocated_size()));
return frame;
}
void OIIODecoder::close_internal()
{
close_image_handle();
}
bool OIIODecoder::file_type_is_supported(const std::string &fn)
{
// We prioritize OIIO over FFmpeg to pick up still images more effectively, but some OIIO decoders (notably OpenJPEG)
// will segfault entirely if given unexpected data (an MPEG-4 for instance). To workaround this issue, we use OIIO's
// "extension_list" attribute and match it with the extension of the file.
// Check if we've created the supported formats list, create it if not
if (supported_formats.empty()) {
std::vector<std::string> extension_list =
split_string(OIIO::get_string_attribute("extension_list"), ';');
// The format of "extension_list" is "format:ext", we want to separate it into a simple list of extensions
for (const std::string &ext : extension_list) {
std::vector<std::string> format_and_ext = split_string(ext, ':');
if (format_and_ext.size() >= 2) {
std::vector<std::string> exts =
split_string(format_and_ext.at(1), ',');
supported_formats.insert(supported_formats.end(),
exts.begin(), exts.end());
}
}
}
// QFileInfo::suffix(): extension after the last '.', case-insensitive match
std::string suffix = std::filesystem::path(fn).extension().string();
if (!suffix.empty() && suffix.front() == '.') {
suffix.erase(0, 1);
}
suffix = to_lower(suffix);
for (const std::string &supported : supported_formats) {
if (to_lower(supported) == suffix) {
return true;
}
}
return false;
}
bool OIIODecoder::open_image_handler(const std::string &fn, int subimage)
{
image_ = OIIO::ImageInput::open(fn);
if (!image_) {
return false;
}
if (!image_->seek_subimage(subimage, 0)) {
return false;
}
// Check if we can work with this pixel format
const OIIO::ImageSpec &spec = image_->spec();
// We use RGBA frames because that tends to be the native format of GPUs
pix_fmt_ = static_cast<PixelFormat::Format>(
pix_format_from_oiio_basetype(spec.format.basetype));
if (pix_fmt_ == PixelFormat::invalid) {
fprintf(stderr, "Failed to convert OIIO::ImageDesc to native pixel format\n");
return false;
}
oiio_pix_fmt_ =
static_cast<OIIO::TypeDesc::BASETYPE>(
oiio_base_type_from_pix_format(pix_fmt_));
if (oiio_pix_fmt_ == OIIO::TypeDesc::UNKNOWN) {
fprintf(stderr, "Failed to determine appropriate OIIO basetype from native format\n");
return false;
}
return true;
}
void OIIODecoder::close_image_handle()
{
if (image_) {
image_->close();
image_ = nullptr;
}
buffer_.destroy();
}
OakVideoParams
OIIODecoder::get_video_params_from_image_spec(const OIIO::ImageSpec &spec)
{
OakVideoParams video_params = oakcommon_videoparams_init();
oakcommon_videoparams_set_width(video_params, spec.width);
oakcommon_videoparams_set_height(video_params, spec.height);
oakcommon_videoparams_set_format(
video_params, pix_format_from_oiio_basetype(spec.format.basetype));
oakcommon_videoparams_set_channel_count(video_params, spec.nchannels);
int par_num = 1, par_den = 1;
{
OakOIIOUtils utils = oakcommon_oiioutils_init();
oakcommon_oiioutils_get_pixel_aspect_ratio(
utils, spec.get_float_attribute("PixelAspectRatio", 1.0f),
&par_num, &par_den);
oakcommon_oiioutils_free(&utils);
}
oakcommon_videoparams_set_pixel_aspect_ratio(video_params, par_num,
par_den);
oakcommon_videoparams_set_video_type(video_params,
OAKCOMMON_VIDEO_TYPE_STILL);
return video_params;
}
}
+84
View File
@@ -0,0 +1,84 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
Modifications Copyright (C) 2025 mikesolar
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 OAK_OIIODECODER_H
#define OAK_OIIODECODER_H
#include <memory>
#include <string>
#include <vector>
#include <OpenImageIO/imageio.h>
#include <OpenImageIO/imagebuf.h>
#include "decoder.h"
namespace olive
{
class OIIODecoder : public Decoder {
public:
OIIODecoder();
DECODER_DEFAULT_DESTRUCTOR(OIIODecoder)
virtual std::string id() const override;
virtual bool supports_video() override
{
return true;
}
virtual FootageDescription probe(const std::string &filename,
OakCancelAtom *cancelled) const override;
protected:
virtual bool open_internal() override;
virtual OakRenderTexture *
retrieve_video_internal(const RetrieveVideoParams &p) override;
virtual FramePtr
retrieve_video_frame_internal(const RetrieveVideoParams &p) override;
virtual void close_internal() override;
private:
std::unique_ptr<OIIO::ImageInput> image_;
static bool file_type_is_supported(const std::string &fn);
bool open_image_handler(const std::string &fn, int subimage);
void close_image_handle();
static OakVideoParams
get_video_params_from_image_spec(const OIIO::ImageSpec &spec);
PixelFormat pix_fmt_;
OIIO::TypeDesc::BASETYPE oiio_pix_fmt_;
Frame buffer_;
RetrieveVideoParams last_params_;
static std::vector<std::string> supported_formats;
};
}
#endif // OAK_OIIODECODER_H
+92
View File
@@ -0,0 +1,92 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
Modifications Copyright (C) 2025 mikesolar
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/>.
***/
#include "oiioencoder.h"
#include <OpenImageIO/imageio.h>
#include "common/oiioutils.h"
OIIO_NAMESPACE_USING
namespace olive
{
OIIOEncoder::OIIOEncoder(const EncodingParams &params) : Encoder(params) {}
bool OIIOEncoder::open()
{
return true;
}
bool OIIOEncoder::write_frame(FramePtr frame, Rational time)
{
std::string filename = get_filename_for_frame(time);
auto output = OIIO::ImageOutput::create(filename);
if (!output) {
return false;
}
int base_type = 0; // OIIO::TypeDesc::UNKNOWN
OakOIIOUtils oiio_utils = oakcommon_oiioutils_init();
oakcommon_oiioutils_get_oiio_base_type_from_format(
oiio_utils, frame->format(), &base_type);
oakcommon_oiioutils_free(&oiio_utils);
OIIO::TypeDesc type(static_cast<OIIO::TypeDesc::BASETYPE>(base_type));
OIIO::ImageSpec spec(frame->width(), frame->height(),
frame->channel_count(), type);
if (!output->open(filename, spec)) {
return false;
}
if (!output->write_image(type, frame->data(), OIIO::AutoStride,
frame->linesize_bytes())) {
return false;
}
if (!output->close()) {
return false;
}
return true;
}
bool OIIOEncoder::write_audio(const SampleBuffer &audio)
{
// Do nothing
return false;
}
bool OIIOEncoder::write_subtitle(const char *text, double in_seconds,
double out_seconds)
{
return false;
}
void OIIOEncoder::close()
{
// Do nothing
}
}
+47
View File
@@ -0,0 +1,47 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
Modifications Copyright (C) 2025 mikesolar
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 OAK_OIIOENCODER_H
#define OAK_OIIOENCODER_H
#include "encoder.h"
namespace olive
{
class OIIOEncoder : public Encoder {
public:
OIIOEncoder(const EncodingParams &params);
virtual bool open() override;
virtual bool write_frame(olive::FramePtr frame,
olive::core::Rational time) override;
virtual bool write_audio(const SampleBuffer &audio) override;
virtual bool write_subtitle(const char *text, double in_seconds,
double out_seconds) override;
virtual void close() override;
};
}
#endif // OAK_OIIOENCODER_H
+40
View File
@@ -0,0 +1,40 @@
/***
Oak Video Editor - 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/>.
***/
#include "oiioframebridge.h"
namespace olive
{
void oiio_frame_to_buffer(const void *data, int64_t linesize_bytes,
OIIO::ImageBuf *buf)
{
buf->set_pixels(OIIO::ROI(), buf->spec().format, data, OIIO::AutoStride,
static_cast<OIIO::stride_t>(linesize_bytes));
}
void oiio_buffer_to_frame(OIIO::ImageBuf *buf, void *data,
int64_t linesize_bytes)
{
buf->get_pixels(OIIO::ROI(), buf->spec().format, data, OIIO::AutoStride,
static_cast<OIIO::stride_t>(linesize_bytes));
}
}
+52
View File
@@ -0,0 +1,52 @@
/***
Oak Video Editor - 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 OAK_OIIOFRAMEBRIDGE_H
#define OAK_OIIOFRAMEBRIDGE_H
#include <cstdint>
#include <OpenImageIO/imagebuf.h>
namespace olive
{
/**
* @brief Copies raw pixel data into an OIIO image buffer
*
* Moved from oakcommon's OIIOUtils (M5): these two helpers are only used
* by codec (Frame::convert() and the OIIO decoder/encoder), so they live
* here as internal C++ functions and no longer cross the oakcommon
* boundary. `format`/`nb_channels` describe the raw buffer and are only
* needed by callers to have set up `buf`'s spec correctly beforehand;
* the copy itself goes by the buffer's spec.
*/
void oiio_frame_to_buffer(const void *data, int64_t linesize_bytes,
OIIO::ImageBuf *buf);
/**
* @brief Copies an OIIO image buffer's pixels into raw memory
*/
void oiio_buffer_to_frame(OIIO::ImageBuf *buf, void *data,
int64_t linesize_bytes);
}
#endif // OAK_OIIOFRAMEBRIDGE_H
+125
View File
@@ -0,0 +1,125 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
Modifications Copyright (C) 2025 mikesolar
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/>.
***/
#include "planarfiledevice.h"
#include <sys/stat.h>
namespace olive
{
PlanarFileDevice::PlanarFileDevice() = default;
PlanarFileDevice::~PlanarFileDevice()
{
close();
}
bool PlanarFileDevice::open(const std::vector<std::string> &filenames,
OpenMode mode)
{
if (isOpen()) {
// Already open
return false;
}
const char *mode_str = (mode == k_read_only) ? "rb" : "wb";
files_.resize(filenames.size(), nullptr);
for (size_t i = 0; i < files_.size(); i++) {
files_[i] = std::fopen(filenames.at(i).c_str(), mode_str);
if (!files_[i]) {
close();
return false;
}
}
return true;
}
int64_t PlanarFileDevice::read(char **data, int64_t bytes_per_channel,
int64_t offset)
{
int64_t ret = -1;
if (isOpen()) {
for (size_t i = 0; i < files_.size(); i++) {
// Kind of clunky but should be largely fine
ret = int64_t(std::fread(data[i] + offset, 1,
size_t(bytes_per_channel), files_[i]));
}
}
return ret;
}
int64_t PlanarFileDevice::write(const char **data, int64_t bytes_per_channel,
int64_t offset)
{
int64_t ret = -1;
if (isOpen()) {
for (size_t i = 0; i < files_.size(); i++) {
// Kind of clunky but should be largely fine
ret = int64_t(std::fwrite(data[i] + offset, 1,
size_t(bytes_per_channel), files_[i]));
}
}
return ret;
}
int64_t PlanarFileDevice::size() const
{
if (isOpen()) {
struct stat st;
if (fstat(fileno(files_.front()), &st) == 0) {
return int64_t(st.st_size);
}
}
return 0;
}
bool PlanarFileDevice::seek(int64_t pos)
{
bool ret = true;
for (size_t i = 0; i < files_.size(); i++) {
ret = (std::fseek(files_[i], pos, SEEK_SET) == 0) && ret;
}
return ret;
}
void PlanarFileDevice::close()
{
for (size_t i = 0; i < files_.size(); i++) {
std::FILE *f = files_.at(i);
if (f) {
std::fclose(f);
}
}
files_.clear();
}
}
+77
View File
@@ -0,0 +1,77 @@
/***
Olive - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
Modifications Copyright (C) 2025 mikesolar
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 OAK_PLANARFILEDEVICE_H
#define OAK_PLANARFILEDEVICE_H
#include <cstdint>
#include <cstdio>
#include <string>
#include <vector>
namespace olive
{
/**
* @brief Reads/writes interleaved planar channel files
*
* De-Qt replacement for the QFile-based version; now a thin wrapper over
* std::FILE. Not copyable; closes all files on destruction.
*/
class PlanarFileDevice {
public:
/**
* @brief Open mode (replaces QIODevice::OpenMode)
*/
enum OpenMode { k_read_only, k_write_only };
PlanarFileDevice();
~PlanarFileDevice();
PlanarFileDevice(const PlanarFileDevice &) = delete;
PlanarFileDevice &operator=(const PlanarFileDevice &) = delete;
bool isOpen() const
{
return !files_.empty();
}
bool open(const std::vector<std::string> &filenames, OpenMode mode);
int64_t read(char **data, int64_t bytes_per_channel, int64_t offset = 0);
int64_t write(const char **data, int64_t bytes_per_channel,
int64_t offset = 0);
int64_t size() const;
bool seek(int64_t pos);
void close();
private:
std::vector<std::FILE *> files_;
};
}
#endif // OAK_PLANARFILEDEVICE_H
+301
View File
@@ -0,0 +1,301 @@
/*
* Oak Video Editor - 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/>.
*/
#include "proxymanager.h"
#include <cstdlib>
#include <filesystem>
#include <vector>
#include <unistd.h>
#include "common/filefunctions.h"
#include "taskcallbacks.h"
namespace olive
{
ProxyManager *ProxyManager::instance_ = nullptr;
namespace
{
/**
* @brief oakcommon C API wrapper for FileFunctions::get_unique_file_identifier
*/
std::string unique_file_identifier(const std::string &filename)
{
OakFileFunctions ff = oakcommon_filefunctions_init();
if (!ff.ctx) {
return std::string();
}
std::string result;
int size = oakcommon_filefunctions_get_unique_file_identifier(
ff, filename.c_str(), nullptr, 0);
if (size > 0) {
result.resize(size_t(size) - 1); // size includes the NUL
oakcommon_filefunctions_get_unique_file_identifier(
ff, filename.c_str(), result.data(), size);
}
oakcommon_filefunctions_free(&ff);
return result;
}
/**
* @brief oakcommon C API wrapper for FileFunctions::get_application_path
*/
std::string application_path()
{
OakFileFunctions ff = oakcommon_filefunctions_init();
if (!ff.ctx) {
return std::string();
}
std::string result;
int size = oakcommon_filefunctions_get_application_path(ff, nullptr, 0);
if (size > 0) {
result.resize(size_t(size) - 1);
oakcommon_filefunctions_get_application_path(ff, result.data(), size);
}
oakcommon_filefunctions_free(&ff);
return result;
}
bool is_executable_file(const std::filesystem::path &p)
{
std::error_code ec;
return std::filesystem::is_regular_file(p, ec) &&
::access(p.c_str(), X_OK) == 0;
}
} // namespace
std::string ProxyManager::get_proxy_directory(const std::string &cache_path)
{
return (std::filesystem::path(cache_path) / "proxy").string();
}
std::string ProxyManager::get_proxy_filename(const std::string &cache_path,
const std::string &source_filename,
int stream_index,
const ProxyParams &params)
{
const std::string proxy_dir = get_proxy_directory(cache_path);
const std::string extension =
params.extension.empty() ? "mp4" : params.extension;
// Divider mode scales relative to the source, so the tag names the
// divider rather than an absolute target size
std::string size_tag;
if (params.divider > 1) {
size_tag = "div" + std::to_string(params.divider);
} else {
size_tag = std::to_string(params.width) + "x" +
std::to_string(params.height);
}
const std::string filename = unique_file_identifier(source_filename) + "-" +
std::to_string(stream_index) + "." + size_tag +
".v" + std::to_string(params.version) + ".a" +
(params.include_audio ? "1" : "0") + "." +
extension;
return (std::filesystem::path(proxy_dir) / filename).string();
}
std::string ProxyManager::get_working_proxy_filename(const std::string &proxy_filename)
{
// Append a recognizable suffix while keeping a standard container extension
// so ffmpeg can infer the output format.
return proxy_filename + ".working.mp4";
}
ProxyManager::ProxyState
ProxyManager::get_proxy_state(const std::string &proxy_filename)
{
std::error_code ec;
if (std::filesystem::exists(proxy_filename, ec)) {
return k_proxy_ready;
}
if (std::filesystem::exists(get_working_proxy_filename(proxy_filename), ec)) {
return k_proxy_generating;
}
return k_proxy_missing;
}
std::string ProxyManager::proxy_state_to_string(ProxyState state)
{
switch (state) {
case k_proxy_missing:
return "missing";
case k_proxy_generating:
return "generating";
case k_proxy_ready:
return "ready";
case k_proxy_failed:
return "failed";
}
return "missing";
}
ProxyManager::ProxyState
ProxyManager::proxy_state_from_string(const std::string &state)
{
if (state == "generating") {
return k_proxy_generating;
}
if (state == "ready") {
return k_proxy_ready;
}
if (state == "failed") {
return k_proxy_failed;
}
return k_proxy_missing;
}
bool ProxyManager::proxy_filename_has_audio(const std::string &proxy_filename)
{
return std::filesystem::path(proxy_filename)
.filename()
.string()
.find(".a1.") != std::string::npos;
}
ProxyManager::ProxyParams ProxyManager::proxy_params_from_config()
{
// Interim state: the Qt config store (OAK_CONFIG ProxyWidth/ProxyHeight/
// ProxyDivider/ProxyCRF/ProxyPreset/ProxyIncludeAudio) is not split yet,
// so the compiled-in defaults apply.
return ProxyParams();
}
std::string ProxyManager::find_f_fmpeg_executable(const std::string &configured_path)
{
// An explicitly configured path takes precedence if it is usable
if (!configured_path.empty()) {
if (is_executable_file(configured_path)) {
return std::filesystem::absolute(configured_path).string();
}
fprintf(stderr, "Configured ffmpeg path is not a valid executable: %s\n",
configured_path.c_str());
}
// Fall back to searching the system PATH
if (const char *path_env = std::getenv("PATH")) {
std::string paths = path_env;
size_t pos = 0;
while (pos <= paths.size()) {
size_t colon = paths.find(':', pos);
std::string dir = paths.substr(
pos, colon == std::string::npos ? colon : colon - pos);
if (!dir.empty()) {
std::filesystem::path candidate = std::filesystem::path(dir) / "ffmpeg";
if (is_executable_file(candidate)) {
return candidate.string();
}
}
if (colon == std::string::npos) {
break;
}
pos = colon + 1;
}
}
// Finally, try common install locations (PATH on GUI-launched apps,
// particularly on macOS, often lacks these)
std::vector<std::string> candidates;
const std::string app_path = application_path();
if (!app_path.empty()) {
candidates.push_back(app_path + "/ffmpeg");
}
#ifdef __APPLE__
candidates.push_back("/opt/homebrew/bin/ffmpeg");
candidates.push_back("/usr/local/bin/ffmpeg");
#endif
candidates.push_back("/usr/bin/ffmpeg");
candidates.push_back("/usr/local/bin/ffmpeg");
for (const std::string &candidate : candidates) {
if (is_executable_file(candidate)) {
return std::filesystem::absolute(candidate).string();
}
}
return std::string();
}
ProxyManager::Proxy
ProxyManager::get_or_start_proxy(const std::string &cache_path,
const std::string &source_filename, int stream_index,
const ProxyParams &params)
{
const std::string filename =
get_proxy_filename(cache_path, source_filename, stream_index, params);
const ProxyState file_state = get_proxy_state(filename);
if (file_state == k_proxy_ready) {
return { k_proxy_ready, filename };
}
if (!oakcodec_task_submit_is_registered()) {
// Interim state (pre-M8): no task system, proxy cannot be generated
return { k_proxy_missing, filename };
}
if (file_state == k_proxy_generating) {
// Stale working file from an interrupted run
std::error_code ec;
std::filesystem::remove(get_working_proxy_filename(filename), ec);
}
// The task owns the ".working.mp4" temporary name and the rename to the
// final filename on success (previously done in proxy_task_finished).
OakCodecTaskRequest req = {};
req.kind = OAKCODEC_TASK_PROXY;
req.input_filename = source_filename.c_str();
req.output_filename = filename.c_str();
req.stream_index = stream_index;
if (params.divider <= 1) {
req.proxy_width = params.width;
req.proxy_height = params.height;
}
// Interim simplification: submission is synchronous.
int result = SubmitTask(req);
if (result < 0) {
return { k_proxy_failed, filename };
}
if (get_proxy_state(filename) == k_proxy_ready) {
return { k_proxy_ready, filename };
}
return { k_proxy_generating, filename };
}
} // namespace olive
+142
View File
@@ -0,0 +1,142 @@
/*
* Oak Video Editor - 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 OAK_PROXYMANAGER_H
#define OAK_PROXYMANAGER_H
#include <string>
namespace olive
{
/**
* @brief Manages proxy (lower-res stand-in) generation
*
* Qt-free interim state: actual proxy transcodes are delegated to the
* global task submit callback (include/codec/task.h). While no callback
* is registered (pre-M8), get_or_start_proxy() reports the proxy as
* missing instead of starting background work.
*
* Behavior changes vs. the Qt version:
* - The `proxy_ready`/`proxy_finished` signals are gone; completion
* notification is the task system's / facade's business.
* - Submission is synchronous: get_or_start_proxy() calls the submit
* callback inline and re-derives the state from the filesystem.
* - proxy_params_from_config() returns compiled-in defaults until the
* config milestone wires a real store.
*/
class ProxyManager {
public:
static void create_instance()
{
if (!instance_) {
instance_ = new ProxyManager();
}
}
static void destroy_instance()
{
delete instance_;
instance_ = nullptr;
}
static ProxyManager *instance()
{
return instance_;
}
enum ProxyState {
k_proxy_missing,
k_proxy_generating,
k_proxy_ready,
k_proxy_failed
};
struct ProxyParams {
int width = 1280;
int height = 720;
/**
* @brief Source resolution divider (1 = use absolute width/height,
* 2/4/8 = fraction of the source resolution)
*/
int divider = 1;
int version = 1;
std::string extension = "mp4";
int crf = 23;
std::string preset = "veryfast";
bool include_audio = true;
};
struct Proxy {
ProxyState state = k_proxy_missing;
std::string filename;
};
static std::string get_proxy_directory(const std::string &cache_path);
static std::string get_proxy_filename(const std::string &cache_path,
const std::string &source_filename,
int stream_index,
const ProxyParams &params);
static std::string get_working_proxy_filename(const std::string &proxy_filename);
static ProxyState get_proxy_state(const std::string &proxy_filename);
static std::string proxy_state_to_string(ProxyState state);
static ProxyState proxy_state_from_string(const std::string &state);
/**
* @brief Returns true if a proxy filename generated by GetProxyFilename()
* indicates the proxy contains audio streams
*/
static bool proxy_filename_has_audio(const std::string &proxy_filename);
/**
* @brief Builds proxy parameters from the global application config
*
* Interim state: returns the compiled-in defaults (1280x720, divider 1,
* mp4, crf 23, veryfast, audio included); the config milestone wires
* the real store.
*/
static ProxyParams proxy_params_from_config();
/**
* @brief Locates an ffmpeg executable for proxy generation
*
* Resolution order: the explicitly configured path (if non-empty and an
* existing executable file), then the system PATH, then common
* platform-specific install locations. Returns an empty string if no
* executable could be found.
*/
static std::string find_f_fmpeg_executable(const std::string &configured_path);
Proxy get_or_start_proxy(const std::string &cache_path,
const std::string &source_filename, int stream_index,
const ProxyParams &params);
private:
ProxyManager() = default;
static ProxyManager *instance_;
};
} // namespace olive
#endif // OAK_PROXYMANAGER_H
+63
View File
@@ -0,0 +1,63 @@
/*
Oak Video Editor - 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/>.
*/
#include "taskcallbacks.h"
#include <mutex>
namespace
{
std::mutex g_task_cb_mutex;
oakcodec_task_submit_fn g_task_cb = nullptr;
void *g_task_cb_userdata = nullptr;
} // namespace
extern "C" {
void oakcodec_set_task_submit_cb(oakcodec_task_submit_fn cb, void *userdata)
{
std::lock_guard<std::mutex> lock(g_task_cb_mutex);
g_task_cb = cb;
g_task_cb_userdata = userdata;
}
int oakcodec_task_submit_is_registered(void)
{
std::lock_guard<std::mutex> lock(g_task_cb_mutex);
return g_task_cb != nullptr ? 1 : 0;
}
} // extern "C"
namespace olive
{
int SubmitTask(const OakCodecTaskRequest &req)
{
std::lock_guard<std::mutex> lock(g_task_cb_mutex);
if (!g_task_cb) {
return OAKCODEC_E_STATE;
}
return g_task_cb(&req, g_task_cb_userdata);
}
} // namespace olive
+39
View File
@@ -0,0 +1,39 @@
/*
Oak Video Editor - 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 OAK_CODEC_TASKCALLBACKS_H
#define OAK_CODEC_TASKCALLBACKS_H
#include "codec/task.h"
namespace olive
{
/**
* @brief C++-side convenience wrapper around the registered submit callback
*
* Returns the callback's return value, or OAKCODEC_E_STATE when no
* callback is registered (interim pre-M8 state).
*/
int SubmitTask(const OakCodecTaskRequest &req);
} // namespace olive
#endif // OAK_CODEC_TASKCALLBACKS_H
+111
View File
@@ -0,0 +1,111 @@
/***
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/>.
***/
#include "timecodemetadata.h"
#include <cstdlib>
#include <limits>
#include <numeric>
#include "olive/core/util/timecodefunctions.h"
namespace olive
{
namespace
{
std::string trimmed(const std::string &s)
{
const char *ws = " \t\n\r\f\v";
size_t begin = s.find_first_not_of(ws);
if (begin == std::string::npos) {
return std::string();
}
size_t end = s.find_last_not_of(ws);
return s.substr(begin, end - begin + 1);
}
} // namespace
TimecodeMetadata::SourceTime
TimecodeMetadata::from_timecode_string(const std::string &timecode,
const core::Rational &timebase)
{
SourceTime result;
const std::string trimmed_tc = trimmed(timecode);
if (trimmed_tc.empty()) {
return result;
}
bool ok = false;
const core::Timecode::Display display =
trimmed_tc.find(';') != std::string::npos ?
core::Timecode::k_timecode_drop_frame :
core::Timecode::k_timecode_non_drop_frame;
result.time =
core::Timecode::timecode_to_time(trimmed_tc, timebase, display, &ok);
result.valid = ok;
if (ok) {
result.source = "timecode";
}
return result;
}
TimecodeMetadata::SourceTime
TimecodeMetadata::from_bwf_time_reference(const std::string &time_reference,
int sample_rate)
{
SourceTime result;
if (sample_rate <= 0) {
return result;
}
bool ok = false;
const std::string trimmed_ref = trimmed(time_reference);
char *end = nullptr;
const unsigned long long samples =
std::strtoull(trimmed_ref.c_str(), &end, 10);
ok = end != trimmed_ref.c_str() && *end == '\0';
if (!ok) {
return result;
}
unsigned long long numerator = samples;
unsigned long long denominator = static_cast<unsigned long long>(sample_rate);
const unsigned long long divisor = std::gcd(numerator, denominator);
numerator /= divisor;
denominator /= divisor;
const unsigned long long rational_limit =
static_cast<unsigned long long>(std::numeric_limits<int>::max());
if (numerator <= rational_limit && denominator <= rational_limit) {
result.time = core::Rational(static_cast<int>(numerator),
static_cast<int>(denominator));
} else {
result.time = core::Rational::from_double(
static_cast<double>(samples) / static_cast<double>(sample_rate));
}
result.source = "bwf_time_reference";
result.valid = true;
return result;
}
}
+48
View File
@@ -0,0 +1,48 @@
/***
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 OAK_TIMECODEMETADATA_H
#define OAK_TIMECODEMETADATA_H
#include <string>
#include "olive/core/util/rational.h"
namespace olive
{
class TimecodeMetadata {
public:
struct SourceTime {
core::Rational time;
std::string source;
bool valid = false;
};
static SourceTime from_timecode_string(const std::string &timecode,
const core::Rational &timebase);
static SourceTime from_bwf_time_reference(const std::string &time_reference,
int sample_rate);
};
}
#endif // OAK_TIMECODEMETADATA_H
+155
View File
@@ -0,0 +1,155 @@
# Oak Video Editor - 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/>.
# Standalone build driver for the oakcodec module (M5). Mirrors
# src/render/standalone: oakcodec links oakrender (CancelAtom/texture C
# ABI), oakcommon, olivecore and ffmpeg_bridge; references into the
# not-yet-split task/config modules are interim no-ops (task submit
# callback registry, compiled-in proxy defaults), and oakrender's own
# dangling symbols resolve via -undefined dynamic_lookup (macOS).
#
# Usage (macOS/Homebrew):
# cmake -S src/codec/standalone -B build-oakcodec
# cmake --build build-oakcodec -j
# ctest --test-dir build-oakcodec
cmake_minimum_required(VERSION 3.16 FATAL_ERROR)
project(oakcodec-standalone LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
get_filename_component(OAK_REPO_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/../../.." ABSOLUTE)
list(APPEND CMAKE_MODULE_PATH "${OAK_REPO_ROOT}/cmake")
if(EXISTS "/opt/homebrew")
list(APPEND CMAKE_PREFIX_PATH "/opt/homebrew")
endif()
find_package(EXPAT REQUIRED)
find_package(OpenColorIO CONFIG REQUIRED)
find_package(OpenImageIO CONFIG REQUIRED)
set(OCIO_LIBRARIES OpenColorIO::OpenColorIO)
set(OCIO_INCLUDE_DIRS "")
set(OIIO_LIBRARIES OpenImageIO::OpenImageIO)
set(OIIO_INCLUDE_DIRS "")
# In-repo libraries, built from source (same set src/render/standalone
# assembles, because oakcodec links oakrender):
# - olivecore (core/): oakcore_* C ABI and olive::core C++ utils
# - ffmpeg_bridge: fb_* C ABI (the only FFmpeg access codec performs)
# - oakundo / oakcommon / oaknode: oakrender's own dependencies
# - oakrender: CancelAtom + texture C ABI consumed by codec
set(OLIVECORE_BUILD_TESTS OFF CACHE BOOL "" FORCE)
set(CMAKE_DISABLE_FIND_PACKAGE_OpenTimelineIO ON)
add_subdirectory(${OAK_REPO_ROOT}/core ${CMAKE_BINARY_DIR}/core)
target_include_directories(olivecore PUBLIC ${OAK_REPO_ROOT}/third_party/openfx/include)
add_subdirectory(${OAK_REPO_ROOT}/ffmpeg_bridge ${CMAKE_BINARY_DIR}/ffmpeg_bridge)
set(BUILD_TESTS OFF)
add_subdirectory(${OAK_REPO_ROOT}/src/undo ${CMAKE_BINARY_DIR}/undo)
add_subdirectory(${OAK_REPO_ROOT}/src/common ${CMAKE_BINARY_DIR}/common)
add_subdirectory(${OAK_REPO_ROOT}/src/node ${CMAKE_BINARY_DIR}/node)
set(BUILD_TESTS ON)
# oaknode needs its transition stubs when built in this tree (see
# src/render/standalone/CMakeLists.txt).
target_include_directories(oaknode BEFORE PUBLIC
${OAK_REPO_ROOT}/src/render/transition
${OAK_REPO_ROOT}/src/node/transition
${OAK_REPO_ROOT}/src/render/src
)
target_include_directories(oaknode PUBLIC
${OAK_REPO_ROOT}/third_party/openfx/HostSupport/include
/opt/homebrew/include
/opt/homebrew/include/Imath
)
target_link_options(oaknode PRIVATE
"-undefined" "dynamic_lookup"
)
add_subdirectory(${OAK_REPO_ROOT}/src/render/src ${CMAKE_BINARY_DIR}/render)
add_subdirectory(${OAK_REPO_ROOT}/src/render/c_api ${CMAKE_BINARY_DIR}/render_c_api)
# Transition stub dirs must precede everything else: src/render/transition
# first, then src/node/transition (shared stubs).
target_include_directories(oakrender BEFORE PUBLIC
${OAK_REPO_ROOT}/src/render/transition
${OAK_REPO_ROOT}/src/node/transition
)
target_include_directories(oakrender PUBLIC
${OAK_REPO_ROOT}/engine/include
${OAK_REPO_ROOT}/third_party/openfx/HostSupport/include
/opt/homebrew/include
/opt/homebrew/include/Imath
/opt/homebrew/include/OpenEXR
)
# Vulkan headers (Homebrew keg-only vulkan-headers).
if(NOT EXISTS "/opt/homebrew/include/vulkan/vulkan.h")
execute_process(COMMAND brew --prefix vulkan-headers
OUTPUT_VARIABLE VULKAN_HEADERS_PREFIX
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_QUIET)
if(VULKAN_HEADERS_PREFIX AND EXISTS "${VULKAN_HEADERS_PREFIX}/include/vulkan/vulkan.h")
target_include_directories(oakrender PUBLIC "${VULKAN_HEADERS_PREFIX}/include")
endif()
endif()
# Symbols of the not-yet-split engine modules dangle by design. The
# backend libraries resolve most symbols from liboakrender at load time
# and dangle the same way.
foreach(t oakrender oakgl oakgl2 oakvulkan)
if(TARGET ${t})
target_link_options(${t} PRIVATE
"-undefined" "dynamic_lookup"
)
endif()
endforeach()
target_link_libraries(oakrender PRIVATE
oaknode
oakcommon
oakundo
olivecore
ffmpeg_bridge
${OCIO_LIBRARIES}
${OIIO_LIBRARIES}
"-framework OpenGL"
"-framework CoreVideo"
"-framework Metal"
"-framework QuartzCore"
)
# oakcodec itself. Its own dangling references (none expected beyond what
# the linked libraries already dangle) resolve the same way.
add_subdirectory(${OAK_REPO_ROOT}/src/codec/src ${CMAKE_BINARY_DIR}/codec)
add_subdirectory(${OAK_REPO_ROOT}/src/codec/c_api ${CMAKE_BINARY_DIR}/codec_c_api)
target_link_options(oakcodec PRIVATE
"-undefined" "dynamic_lookup"
)
# Tests (oakcodec-gtest).
if(BUILD_TESTS)
enable_testing()
add_subdirectory(${OAK_REPO_ROOT}/src/codec/tests ${CMAKE_BINARY_DIR}/codec_tests)
endif()
+84
View File
@@ -0,0 +1,84 @@
# Oak Video Editor - 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/>.
find_package(GTest REQUIRED)
include(GoogleTest)
# In a full-tree build the repo root is CMAKE_SOURCE_DIR; a standalone
# build (see src/codec/standalone) sets OAK_REPO_ROOT explicitly.
if(NOT DEFINED OAK_REPO_ROOT)
set(OAK_REPO_ROOT ${CMAKE_SOURCE_DIR})
endif()
add_executable(oakcodec-gtest
frame_test.cpp
decoder_test.cpp
encoder_test.cpp
task_test.cpp
)
target_link_libraries(oakcodec-gtest PRIVATE
oakcodec
oakrender
oaknode
oakcommon
oakundo
olivecore
GTest::gtest
GTest::gtest_main
)
# liboakrender/liboaknode dangle OFX host symbols (-undefined
# dynamic_lookup); force-load the host support archive into the test
# process so dyld finds them in the flat namespace at startup. Mirrors
# src/render/tests/CMakeLists.txt.
if(NOT DEFINED OAKRENDER_OFX_HOST_ARCHIVE)
find_library(OAKRENDER_OFX_HOST_ARCHIVE NAMES OfxHost
PATHS ${OAK_REPO_ROOT}/build/third_party/openfx/HostSupport)
endif()
if(NOT OAKRENDER_OFX_HOST_ARCHIVE)
message(FATAL_ERROR
"libOfxHost.a not found; run the full-tree build once or set "
"OAKRENDER_OFX_HOST_ARCHIVE")
endif()
target_link_options(oakcodec-gtest PRIVATE
"-Wl,-force_load,${OAKRENDER_OFX_HOST_ARCHIVE}")
# liboakrender references the oakengine_ipc_* C ABI (worker IPC) via
# dynamic_lookup; the test binary links the inert shim from
# src/node/standalone instead.
target_sources(oakcodec-gtest PRIVATE
${OAK_REPO_ROOT}/src/node/standalone/oakengine_ipc_shim.cpp)
target_include_directories(oakcodec-gtest PRIVATE
${OAK_REPO_ROOT}/engine/include
)
# include/ must win over the render/node transition dirs that leak in
# through oaknode's PUBLIC includes: they carry codec/*.h stubs that would
# otherwise shadow the real oakcodec public headers. -iquote is searched
# before every -I for quoted includes.
target_compile_options(oakcodec-gtest PRIVATE
"-iquote" "${OAK_REPO_ROOT}/include"
)
# tests/demo.mp4 lives at the repo's shared tests directory.
target_compile_definitions(oakcodec-gtest PRIVATE
OAKCODEC_TEST_DATA_DIR="${OAK_REPO_ROOT}/tests")
gtest_discover_tests(oakcodec-gtest
DISCOVERY_MODE PRE_TEST
PROPERTIES ENVIRONMENT
"OCIO=${OAK_REPO_ROOT}/engine/render/ocioconf/config.ocio")
+180
View File
@@ -0,0 +1,180 @@
/***
Oak Video Editor - 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/>.
***/
#include <gtest/gtest.h>
#include <cstdio>
#include <string>
#include "codec/decoder.h"
#ifndef OAKCODEC_TEST_DATA_DIR
#define OAKCODEC_TEST_DATA_DIR "tests"
#endif
namespace
{
std::string demo_path()
{
return std::string(OAKCODEC_TEST_DATA_DIR) + "/demo.mp4";
}
bool demo_exists()
{
FILE *f = fopen(demo_path().c_str(), "rb");
if (f) {
fclose(f);
return true;
}
return false;
}
} // namespace
TEST(OakCodecDecoder, ProbeDemoMp4)
{
if (!demo_exists()) {
GTEST_SKIP() << "tests/demo.mp4 not available";
}
int before = oakcodec_debug_alive_count();
OakDecoder probe = oakcodec_decoder_probe(demo_path().c_str());
ASSERT_NE(probe.ctx, nullptr);
char name[64] = {};
EXPECT_GT(oakcodec_decoder_probe_decoder_name(probe, name, sizeof(name)),
0);
EXPECT_STRNE(name, "");
int video_count = oakcodec_decoder_probe_video_stream_count(probe);
EXPECT_GE(video_count, 1);
if (video_count >= 1) {
oakcodec_video_stream_info info = {};
ASSERT_EQ(oakcodec_decoder_probe_get_video_stream(probe, 0, &info),
OAKCODEC_OK);
EXPECT_GT(info.width, 0);
EXPECT_GT(info.height, 0);
EXPECT_GT(info.time_base_den, 0);
// Out-of-range index
EXPECT_EQ(oakcodec_decoder_probe_get_video_stream(
probe, video_count, &info),
OAKCODEC_E_NOT_FOUND);
}
// Audio stream enumeration must not crash (count may be 0)
int audio_count = oakcodec_decoder_probe_audio_stream_count(probe);
EXPECT_GE(audio_count, 0);
if (audio_count >= 1) {
oakcodec_audio_stream_info ainfo = {};
ASSERT_EQ(oakcodec_decoder_probe_get_audio_stream(probe, 0, &ainfo),
OAKCODEC_OK);
EXPECT_GT(ainfo.sample_rate, 0);
}
oakcodec_decoder_free(&probe);
EXPECT_EQ(probe.ctx, nullptr);
EXPECT_EQ(oakcodec_debug_alive_count(), before);
}
TEST(OakCodecDecoder, ProbeMissingFile)
{
OakDecoder probe =
oakcodec_decoder_probe("/nonexistent/path/to/file.mp4");
EXPECT_EQ(probe.ctx, nullptr);
char err[256] = {};
EXPECT_GT(oakcodec_probe_last_error(err, sizeof(err)), 1);
EXPECT_STRNE(err, "");
oakcodec_decoder_free(&probe); // no-op
}
TEST(OakCodecDecoder, OpenAndDecodeFirstFrame)
{
if (!demo_exists()) {
GTEST_SKIP() << "tests/demo.mp4 not available";
}
// Find the first video stream index via a probe.
OakDecoder probe = oakcodec_decoder_probe(demo_path().c_str());
ASSERT_NE(probe.ctx, nullptr);
ASSERT_GE(oakcodec_decoder_probe_video_stream_count(probe), 1);
oakcodec_video_stream_info info = {};
ASSERT_EQ(oakcodec_decoder_probe_get_video_stream(probe, 0, &info),
OAKCODEC_OK);
int stream_index = info.stream_index;
oakcodec_decoder_free(&probe);
int before = oakcodec_debug_alive_count();
OakDecoder d = oakcodec_decoder_init();
ASSERT_NE(d.ctx, nullptr);
EXPECT_EQ(oakcodec_decoder_is_open(d), 0);
ASSERT_EQ(oakcodec_decoder_open(d, demo_path().c_str(), stream_index),
OAKCODEC_OK);
EXPECT_EQ(oakcodec_decoder_is_open(d), 1);
OakFrame frame = oakcodec_decoder_decode_video(d, 0, 1);
ASSERT_NE(frame.ctx, nullptr);
EXPECT_EQ(oakcodec_frame_is_allocated(frame), 1);
EXPECT_NE(oakcodec_frame_const_data(frame), nullptr);
EXPECT_GT(oakcodec_frame_width(frame), 0);
EXPECT_GT(oakcodec_frame_height(frame), 0);
oakcodec_frame_free(&frame);
EXPECT_EQ(oakcodec_decoder_close(d), OAKCODEC_OK);
EXPECT_EQ(oakcodec_decoder_is_open(d), 0);
oakcodec_decoder_free(&d);
EXPECT_EQ(oakcodec_debug_alive_count(), before);
}
TEST(OakCodecDecoder, OpenMissingFile)
{
OakDecoder d = oakcodec_decoder_init();
ASSERT_NE(d.ctx, nullptr);
int rc = oakcodec_decoder_open(d, "/nonexistent/video.mp4", 0);
EXPECT_EQ(rc, OAKCODEC_E_NOT_FOUND);
EXPECT_EQ(oakcodec_decoder_is_open(d), 0);
char err[256] = {};
EXPECT_GT(oakcodec_decoder_last_error(d, err, sizeof(err)), 1);
EXPECT_STRNE(err, "");
oakcodec_decoder_free(&d);
}
TEST(OakCodecDecoder, EmptyHandleSemantics)
{
OakDecoder empty = {};
EXPECT_EQ(oakcodec_decoder_is_open(empty), 0);
EXPECT_EQ(oakcodec_decoder_probe_video_stream_count(empty), 0);
OakFrame f = oakcodec_decoder_decode_video(empty, 0, 1);
EXPECT_EQ(f.ctx, nullptr);
oakcodec_decoder_free(nullptr);
oakcodec_decoder_free(&empty);
}
+143
View File
@@ -0,0 +1,143 @@
/***
Oak Video Editor - 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/>.
***/
#include <gtest/gtest.h>
#include <cstdio>
#include <cstring>
#include <string>
#include "codec/decoder.h"
#include "codec/encoder.h"
// Format/codec values mirror oakengine/encoding.h (olive::ExportFormat /
// olive::ExportCodec).
#define TEST_FORMAT_MPEG4 2
#define TEST_CODEC_H264 1
namespace
{
std::string temp_mp4_path()
{
std::string p = std::string("/tmp/oakcodec_encoder_test.mp4");
remove(p.c_str());
return p;
}
} // namespace
TEST(OakCodecEncoder, EncodeMp4RoundTrip)
{
std::string path = temp_mp4_path();
int before = oakcodec_debug_alive_count();
oakcodec_encoding_params params = {};
snprintf(params.filename, sizeof(params.filename), "%s", path.c_str());
params.format = TEST_FORMAT_MPEG4;
params.video_enabled = 1;
params.video_codec = TEST_CODEC_H264;
params.video_width = 64;
params.video_height = 64;
params.video_time_base_num = 1;
params.video_time_base_den = 25;
params.video_pixel_format = OAKCOMMON_PIXEL_FORMAT_U8;
params.video_interlacing = OAKCODEC_INTERLACE_NONE;
params.video_pixel_aspect_num = 1;
params.video_pixel_aspect_den = 1;
params.video_bit_rate = 200000;
snprintf(params.video_pix_fmt, sizeof(params.video_pix_fmt), "yuv420p");
OakEncoder enc = oakcodec_encoder_init(&params);
ASSERT_NE(enc.ctx, nullptr);
if (oakcodec_encoder_open(enc) != OAKCODEC_OK) {
char err[512] = {};
oakcodec_encoder_last_error(enc, err, sizeof(err));
oakcodec_encoder_free(&enc);
GTEST_SKIP() << "encoder not available in this environment: " << err;
}
// Write 10 solid frames.
OakVideoParams vp = oakcommon_videoparams_init_with_time_base(
64, 64, 1, 25, OAKCOMMON_PIXEL_FORMAT_U8, 4, 1, 1,
OAKCOMMON_VIDEO_INTERLACE_NONE, 1);
OakFrame frame = oakcodec_frame_init_with_params(vp);
oakcommon_videoparams_free(&vp);
ASSERT_NE(frame.ctx, nullptr);
ASSERT_EQ(oakcodec_frame_allocate(frame), OAKCODEC_OK);
int linesize = oakcodec_frame_linesize_bytes(frame);
for (int i = 0; i < 10; i++) {
memset(oakcodec_frame_data(frame), 16 + i * 10,
static_cast<size_t>(linesize) * 64);
ASSERT_EQ(oakcodec_frame_set_timestamp(frame, i, 25), OAKCODEC_OK);
ASSERT_EQ(oakcodec_encoder_write_video(enc, frame), OAKCODEC_OK);
}
EXPECT_EQ(oakcodec_encoder_flush(enc), OAKCODEC_OK);
// Writing after flush is a state error.
EXPECT_EQ(oakcodec_encoder_write_video(enc, frame), OAKCODEC_E_STATE);
oakcodec_frame_free(&frame);
oakcodec_encoder_free(&enc);
EXPECT_EQ(oakcodec_debug_alive_count(), before);
// Round-trip: the decoder must open the file and decode a frame.
OakDecoder probe = oakcodec_decoder_probe(path.c_str());
ASSERT_NE(probe.ctx, nullptr);
ASSERT_GE(oakcodec_decoder_probe_video_stream_count(probe), 1);
oakcodec_video_stream_info info = {};
ASSERT_EQ(oakcodec_decoder_probe_get_video_stream(probe, 0, &info),
OAKCODEC_OK);
EXPECT_EQ(info.width, 64);
EXPECT_EQ(info.height, 64);
int stream_index = info.stream_index;
oakcodec_decoder_free(&probe);
OakDecoder dec = oakcodec_decoder_init();
ASSERT_NE(dec.ctx, nullptr);
ASSERT_EQ(oakcodec_decoder_open(dec, path.c_str(), stream_index),
OAKCODEC_OK);
OakFrame decoded = oakcodec_decoder_decode_video(dec, 0, 1);
ASSERT_NE(decoded.ctx, nullptr);
EXPECT_NE(oakcodec_frame_const_data(decoded), nullptr);
oakcodec_frame_free(&decoded);
oakcodec_decoder_free(&dec);
remove(path.c_str());
EXPECT_EQ(oakcodec_debug_alive_count(), before);
}
TEST(OakCodecEncoder, EmptyHandleSemantics)
{
OakEncoder empty = {};
EXPECT_EQ(oakcodec_encoder_open(empty), OAKCODEC_E_INVALID);
EXPECT_EQ(oakcodec_encoder_flush(empty), OAKCODEC_E_INVALID);
oakcodec_encoder_free(nullptr);
oakcodec_encoder_free(&empty);
// An all-disabled params struct is invalid -> empty handle.
oakcodec_encoding_params params = {};
OakEncoder enc = oakcodec_encoder_init(&params);
EXPECT_EQ(enc.ctx, nullptr);
}
+157
View File
@@ -0,0 +1,157 @@
/***
Oak Video Editor - 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/>.
***/
#include <gtest/gtest.h>
#include "codec/frame.h"
namespace
{
OakVideoParams make_params(int width, int height)
{
return oakcommon_videoparams_init_with_time_base(
width, height, 1001, 30000, OAKCOMMON_PIXEL_FORMAT_U8, 4, 1, 1,
OAKCOMMON_VIDEO_INTERLACE_NONE, 1);
}
} // namespace
TEST(OakCodecFrame, InitAndFree)
{
int before = oakcodec_debug_alive_count();
OakFrame f = oakcodec_frame_init();
ASSERT_NE(f.ctx, nullptr);
EXPECT_EQ(f.abi_version, OAKCODEC_ABI_VERSION);
EXPECT_EQ(oakcodec_debug_alive_count(), before + 1);
oakcodec_frame_free(&f);
EXPECT_EQ(f.ctx, nullptr);
EXPECT_EQ(oakcodec_debug_alive_count(), before);
// NULL / empty no-ops
oakcodec_frame_free(nullptr);
oakcodec_frame_free(&f);
}
TEST(OakCodecFrame, ParamsRoundTrip)
{
OakFrame f = oakcodec_frame_init();
ASSERT_NE(f.ctx, nullptr);
OakVideoParams p = make_params(320, 240);
ASSERT_EQ(oakcodec_frame_set_params(f, p), OAKCODEC_OK);
oakcommon_videoparams_free(&p);
OakVideoParams out = {};
ASSERT_EQ(oakcodec_frame_get_params(f, &out), OAKCODEC_OK);
int w = 0, h = 0, fmt = -2, ch = 0, tb_num = 0, tb_den = 0;
oakcommon_videoparams_get_width(out, &w);
oakcommon_videoparams_get_height(out, &h);
oakcommon_videoparams_get_format(out, &fmt);
oakcommon_videoparams_get_channel_count(out, &ch);
oakcommon_videoparams_get_time_base(out, &tb_num, &tb_den);
oakcommon_videoparams_free(&out);
EXPECT_EQ(w, 320);
EXPECT_EQ(h, 240);
EXPECT_EQ(fmt, OAKCOMMON_PIXEL_FORMAT_U8);
EXPECT_EQ(ch, 4);
EXPECT_EQ(tb_num, 1001);
EXPECT_EQ(tb_den, 30000);
oakcodec_frame_free(&f);
}
TEST(OakCodecFrame, AllocateDataLinesize)
{
OakVideoParams p = make_params(64, 48);
OakFrame f = oakcodec_frame_init_with_params(p);
oakcommon_videoparams_free(&p);
ASSERT_NE(f.ctx, nullptr);
EXPECT_EQ(oakcodec_frame_is_allocated(f), 0);
EXPECT_EQ(oakcodec_frame_data(f), nullptr);
ASSERT_EQ(oakcodec_frame_allocate(f), OAKCODEC_OK);
EXPECT_EQ(oakcodec_frame_is_allocated(f), 1);
ASSERT_NE(oakcodec_frame_data(f), nullptr);
EXPECT_EQ(oakcodec_frame_const_data(f), oakcodec_frame_data(f));
// u8 rgba = 4 bytes/px, width 64 aligned to 32 -> 64 * 4
EXPECT_EQ(oakcodec_frame_linesize_bytes(f), 64 * 4);
EXPECT_EQ(oakcodec_frame_linesize_pixels(f), 64);
EXPECT_EQ(oakcodec_frame_allocated_size(f), 64 * 4 * 48);
EXPECT_EQ(oakcodec_frame_width(f), 64);
EXPECT_EQ(oakcodec_frame_height(f), 48);
EXPECT_EQ(oakcodec_frame_format(f), OAKCOMMON_PIXEL_FORMAT_U8);
EXPECT_EQ(oakcodec_frame_channel_count(f), 4);
// Allocating again is a successful no-op
EXPECT_EQ(oakcodec_frame_allocate(f), OAKCODEC_OK);
oakcodec_frame_free(&f);
}
TEST(OakCodecFrame, RefCounting)
{
int before = oakcodec_debug_alive_count();
OakFrame f = oakcodec_frame_init();
ASSERT_NE(f.ctx, nullptr);
// Copy the struct and addref: two references, one object.
OakFrame copy = f;
copy.addref(copy.ctx);
// Release the copy; object stays alive.
copy.release(copy.ctx);
EXPECT_EQ(oakcodec_debug_alive_count(), before + 1);
EXPECT_EQ(oakcodec_frame_width(f), 0); // still valid, default params
oakcodec_frame_free(&f);
EXPECT_EQ(oakcodec_debug_alive_count(), before);
}
TEST(OakCodecFrame, Timestamp)
{
OakFrame f = oakcodec_frame_init();
ASSERT_NE(f.ctx, nullptr);
ASSERT_EQ(oakcodec_frame_set_timestamp(f, 1001, 30000), OAKCODEC_OK);
int num = 0, den = 0;
ASSERT_EQ(oakcodec_frame_get_timestamp(f, &num, &den), OAKCODEC_OK);
EXPECT_EQ(num, 1001);
EXPECT_EQ(den, 30000);
oakcodec_frame_free(&f);
}
TEST(OakCodecFrame, EmptyHandleSemantics)
{
OakFrame empty = {};
EXPECT_EQ(oakcodec_frame_get_params(empty, nullptr), OAKCODEC_E_INVALID);
EXPECT_EQ(oakcodec_frame_allocate(empty), OAKCODEC_E_INVALID);
EXPECT_EQ(oakcodec_frame_is_allocated(empty), 0);
EXPECT_EQ(oakcodec_frame_data(empty), nullptr);
EXPECT_EQ(oakcodec_frame_width(empty), 0);
}
+184
View File
@@ -0,0 +1,184 @@
/***
Oak Video Editor - 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/>.
***/
#include <gtest/gtest.h>
#include <cstring>
#include <string>
#include "codec/conform.h"
#include "codec/proxy.h"
#include "codec/task.h"
namespace
{
struct SubmitLog {
int calls = 0;
OakCodecTaskRequest last = {};
std::string input;
std::string output;
};
int recording_submit(const OakCodecTaskRequest *req, void *userdata)
{
auto *log = static_cast<SubmitLog *>(userdata);
log->calls++;
log->last = *req;
log->input = req->input_filename ? req->input_filename : "";
log->output = req->output_filename ? req->output_filename : "";
// Accept the task but do no work (files never appear).
return OAKCODEC_OK;
}
struct TaskRegistrarGuard {
TaskRegistrarGuard() { oakcodec_set_task_submit_cb(nullptr, nullptr); }
~TaskRegistrarGuard() { oakcodec_set_task_submit_cb(nullptr, nullptr); }
};
} // namespace
TEST(OakCodecTask, RegistryRoundTrip)
{
TaskRegistrarGuard guard;
EXPECT_EQ(oakcodec_task_submit_is_registered(), 0);
SubmitLog log;
oakcodec_set_task_submit_cb(&recording_submit, &log);
EXPECT_EQ(oakcodec_task_submit_is_registered(), 1);
oakcodec_set_task_submit_cb(nullptr, nullptr);
EXPECT_EQ(oakcodec_task_submit_is_registered(), 0);
}
TEST(OakCodecConform, UnregisteredReportsUnavailable)
{
TaskRegistrarGuard guard;
ASSERT_EQ(oakcodec_conform_create_instance(), OAKCODEC_OK);
int state = oakcodec_conform_get_state("/tmp/oakcodec_conform_test",
"some_video.mp4", 1, 48000, 0x3, 4,
1);
EXPECT_EQ(state, OAKCODEC_CONFORM_UNAVAILABLE);
// Filename computation is still deterministic without a registrar.
int count = oakcodec_conform_filename_count(
"/tmp/oakcodec_conform_test", "some_video.mp4", 1, 48000, 0x3, 4);
EXPECT_GE(count, 1); // stereo layout -> 2 channels
if (count >= 1) {
char buf[1024] = {};
EXPECT_GT(oakcodec_conform_filename_at(
"/tmp/oakcodec_conform_test", "some_video.mp4", 1,
48000, 0x3, 4, 0, buf, sizeof(buf)),
1);
EXPECT_STRNE(buf, "");
EXPECT_EQ(oakcodec_conform_filename_at(
"/tmp/oakcodec_conform_test", "some_video.mp4", 1,
48000, 0x3, 4, count, buf, sizeof(buf)),
OAKCODEC_E_NOT_FOUND);
}
oakcodec_conform_destroy_instance();
}
TEST(OakCodecConform, RegisteredSubmitIsInvoked)
{
TaskRegistrarGuard guard;
SubmitLog log;
oakcodec_set_task_submit_cb(&recording_submit, &log);
ASSERT_EQ(oakcodec_conform_create_instance(), OAKCODEC_OK);
// wait=0: the (no-op) task was "queued" -> GENERATING.
int state = oakcodec_conform_get_state("/tmp/oakcodec_conform_test",
"some_video.mp4", 1, 48000, 0x3, 4,
0);
EXPECT_EQ(state, OAKCODEC_CONFORM_GENERATING);
EXPECT_EQ(log.calls, 1);
EXPECT_EQ(log.last.kind, OAKCODEC_TASK_CONFORM);
EXPECT_EQ(log.last.stream_index, 1);
EXPECT_EQ(log.last.sample_rate, 48000);
// wait=1: post-submit miss -> UNAVAILABLE.
state = oakcodec_conform_get_state("/tmp/oakcodec_conform_test",
"some_video.mp4", 1, 48000, 0x3, 4, 1);
EXPECT_EQ(state, OAKCODEC_CONFORM_UNAVAILABLE);
oakcodec_conform_destroy_instance();
}
TEST(OakCodecProxy, MissingAndStateStrings)
{
TaskRegistrarGuard guard;
ASSERT_EQ(oakcodec_proxy_create_instance(), OAKCODEC_OK);
EXPECT_EQ(oakcodec_proxy_get_state(nullptr), OAKCODEC_PROXY_STATE_MISSING);
EXPECT_EQ(oakcodec_proxy_get_state("/nonexistent/proxy.mp4"),
OAKCODEC_PROXY_STATE_MISSING);
char buf[64] = {};
EXPECT_GT(oakcodec_proxy_state_to_string(OAKCODEC_PROXY_STATE_READY, buf,
sizeof(buf)),
1);
EXPECT_EQ(oakcodec_proxy_state_to_string(99, buf, sizeof(buf)),
OAKCODEC_E_INVALID);
oakcodec_proxy_params params = {};
ASSERT_EQ(oakcodec_proxy_params_default(&params), OAKCODEC_OK);
EXPECT_GT(params.width, 0);
EXPECT_STRNE(params.extension, "");
oakcodec_proxy_result result = {};
ASSERT_EQ(oakcodec_proxy_get_or_start("/tmp/oakcodec_proxy_test",
"some_video.mp4", 0, &params, &result),
OAKCODEC_OK);
// No registrar: stays missing, filename is still computed.
EXPECT_EQ(result.state, OAKCODEC_PROXY_STATE_MISSING);
EXPECT_STRNE(result.filename, "");
oakcodec_proxy_destroy_instance();
}
TEST(OakCodecProxy, RegisteredSubmitIsInvoked)
{
TaskRegistrarGuard guard;
SubmitLog log;
oakcodec_set_task_submit_cb(&recording_submit, &log);
ASSERT_EQ(oakcodec_proxy_create_instance(), OAKCODEC_OK);
oakcodec_proxy_params params = {};
ASSERT_EQ(oakcodec_proxy_params_default(&params), OAKCODEC_OK);
oakcodec_proxy_result result = {};
ASSERT_EQ(oakcodec_proxy_get_or_start("/tmp/oakcodec_proxy_test",
"some_video.mp4", 2, &params, &result),
OAKCODEC_OK);
EXPECT_EQ(log.calls, 1);
EXPECT_EQ(log.last.kind, OAKCODEC_TASK_PROXY);
EXPECT_EQ(log.last.stream_index, 2);
// Task accepted but produced nothing -> generating.
EXPECT_EQ(result.state, OAKCODEC_PROXY_STATE_GENERATING);
oakcodec_proxy_destroy_instance();
}
+62 -30
View File
@@ -23,10 +23,15 @@
#include <cstring>
#include "../src/colortransform.h"
#include "refcounted.h"
struct OakCommonColorTransform {
olive::ColorTransform impl;
};
/**
* @brief Recover the boxed olive::ColorTransform from a handle (NULL-safe).
*/
static olive::ColorTransform *ct(OakColorTransform transform)
{
return oakcommon::handle_impl<olive::ColorTransform>(transform.ctx);
}
static int copy_string(const std::string &value, char *buf, int buf_size)
{
@@ -36,89 +41,116 @@ static int copy_string(const std::string &value, char *buf, int buf_size)
return needed;
}
OakCommonColorTransform *oakcommon_colortransform_init_output(
OakColorTransform oakcommon_colortransform_init_output(
const char *output)
{
OakColorTransform h = {};
if (!output)
return nullptr;
return h;
try {
return new OakCommonColorTransform{
olive::ColorTransform(std::string(output))};
return oakcommon::make_handle<OakColorTransform>(
olive::ColorTransform(std::string(output)));
} catch (...) {
return nullptr;
OakColorTransform empty = {};
return empty;
}
}
OakCommonColorTransform *oakcommon_colortransform_init_display(
OakColorTransform oakcommon_colortransform_init_display(
const char *display, const char *view, const char *look)
{
OakColorTransform h = {};
if (!display || !view || !look)
return nullptr;
return h;
try {
return new OakCommonColorTransform{olive::ColorTransform(
std::string(display), std::string(view), std::string(look))};
return oakcommon::make_handle<OakColorTransform>(
olive::ColorTransform(std::string(display), std::string(view),
std::string(look)));
} catch (...) {
return nullptr;
OakColorTransform empty = {};
return empty;
}
}
void oakcommon_colortransform_free(OakCommonColorTransform *transform)
OakColorTransform oakcommon_colortransform_init_from_native(
const olive::ColorTransform *src)
{
delete transform;
if (!src) {
OakColorTransform h = {};
return h;
}
try {
return oakcommon::make_handle<OakColorTransform>(
olive::ColorTransform(*src));
} catch (...) {
OakColorTransform h = {};
return h;
}
}
int oakcommon_colortransform_is_display(OakCommonColorTransform *transform,
const olive::ColorTransform *oakcommon_colortransform_get_native(
OakColorTransform transform)
{
return ct(transform);
}
void oakcommon_colortransform_free(OakColorTransform *transform)
{
oakcommon::free_handle(transform);
}
int oakcommon_colortransform_is_display(OakColorTransform transform,
int *is_display)
{
if (!transform || !is_display)
if (!ct(transform) || !is_display)
return OAKCOMMON_E_INVALID;
*is_display = transform->impl.is_display() ? 1 : 0;
*is_display = ct(transform)->is_display() ? 1 : 0;
return OAKCOMMON_OK;
}
int oakcommon_colortransform_get_display(OakCommonColorTransform *transform,
int oakcommon_colortransform_get_display(OakColorTransform transform,
char *buf, int buf_size)
{
if (!transform)
if (!ct(transform))
return OAKCOMMON_E_INVALID;
try {
return copy_string(transform->impl.display(), buf, buf_size);
return copy_string(ct(transform)->display(), buf, buf_size);
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
int oakcommon_colortransform_get_output(OakCommonColorTransform *transform,
int oakcommon_colortransform_get_output(OakColorTransform transform,
char *buf, int buf_size)
{
if (!transform)
if (!ct(transform))
return OAKCOMMON_E_INVALID;
try {
return copy_string(transform->impl.output(), buf, buf_size);
return copy_string(ct(transform)->output(), buf, buf_size);
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
int oakcommon_colortransform_get_view(OakCommonColorTransform *transform,
int oakcommon_colortransform_get_view(OakColorTransform transform,
char *buf, int buf_size)
{
if (!transform)
if (!ct(transform))
return OAKCOMMON_E_INVALID;
try {
return copy_string(transform->impl.view(), buf, buf_size);
return copy_string(ct(transform)->view(), buf, buf_size);
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
int oakcommon_colortransform_get_look(OakCommonColorTransform *transform,
int oakcommon_colortransform_get_look(OakColorTransform transform,
char *buf, int buf_size)
{
if (!transform)
if (!ct(transform))
return OAKCOMMON_E_INVALID;
try {
return copy_string(transform->impl.look(), buf, buf_size);
return copy_string(ct(transform)->look(), buf, buf_size);
} catch (...) {
return OAKCOMMON_E_FAILED;
}
+91 -57
View File
@@ -26,43 +26,79 @@
#include <vector>
#include "../src/commandlineparser.h"
#include "refcounted.h"
struct OakCommonCommandLineParser {
CommandLineParser impl;
};
namespace
{
struct OakCommonCommandLineOption {
/**
* @brief State boxed behind an option handle's ctx pointer.
*
* The option pointer is borrowed: the option itself is owned by the
* parser, so releasing the box never destroys it.
*/
struct OptionState {
CommandLineParser::Option *option;
};
struct OakCommonCommandLinePositionalArgument {
/**
* @brief State boxed behind a positional-argument handle's ctx pointer.
*
* The argument pointer is borrowed: the argument itself is owned by the
* parser, so releasing the box never destroys it.
*/
struct PositionalArgumentState {
CommandLineParser::PositionalArgument *argument;
};
OakCommonCommandLineParser *oakcommon_commandlineparser_init(void)
CommandLineParser *clp(OakCommandLineParser parser)
{
return oakcommon::handle_impl<CommandLineParser>(parser.ctx);
}
CommandLineParser::Option *clo(OakCommandLineOption option)
{
OptionState *state =
oakcommon::handle_impl<OptionState>(option.ctx);
return state ? state->option : nullptr;
}
CommandLineParser::PositionalArgument *clpa(
OakCommandLinePositionalArgument argument)
{
PositionalArgumentState *state =
oakcommon::handle_impl<PositionalArgumentState>(argument.ctx);
return state ? state->argument : nullptr;
}
} // namespace
OakCommandLineParser oakcommon_commandlineparser_init(void)
{
try {
return new (std::nothrow) OakCommonCommandLineParser();
return oakcommon::make_handle_in_place<OakCommandLineParser,
CommandLineParser>();
} catch (...) {
return NULL;
OakCommandLineParser h = {};
return h;
}
}
void oakcommon_commandlineparser_free(OakCommonCommandLineParser *parser)
void oakcommon_commandlineparser_free(OakCommandLineParser *parser)
{
delete parser;
oakcommon::free_handle(parser);
}
int oakcommon_commandlineparser_set_app_info(OakCommonCommandLineParser *parser,
int oakcommon_commandlineparser_set_app_info(OakCommandLineParser parser,
const char *name,
const char *version)
{
if (!parser || !name) {
if (!clp(parser) || !name) {
return OAKCOMMON_E_INVALID;
}
try {
parser->impl.set_app_info(name, version ? version : "");
clp(parser)->set_app_info(name, version ? version : "");
return OAKCOMMON_OK;
} catch (...) {
return OAKCOMMON_E_FAILED;
@@ -70,11 +106,11 @@ int oakcommon_commandlineparser_set_app_info(OakCommonCommandLineParser *parser,
}
int oakcommon_commandlineparser_add_option(
OakCommonCommandLineParser *parser, const char *const *names, int name_count,
OakCommandLineParser parser, const char *const *names, int name_count,
const char *description, int takes_arg, const char *arg_placeholder,
int hidden, OakCommonCommandLineOption **out_option)
int hidden, OakCommandLineOption *out_option)
{
if (!parser || !names || name_count <= 0) {
if (!clp(parser) || !names || name_count <= 0) {
return OAKCOMMON_E_INVALID;
}
@@ -88,18 +124,16 @@ int oakcommon_commandlineparser_add_option(
strings.emplace_back(names[i]);
}
const CommandLineParser::Option *option = parser->impl.add_option(
const CommandLineParser::Option *option = clp(parser)->add_option(
strings, description ? description : "", takes_arg != 0,
arg_placeholder ? arg_placeholder : "", hidden != 0);
if (out_option) {
auto *handle =
new (std::nothrow) OakCommonCommandLineOption();
if (!handle) {
*out_option = oakcommon::make_handle<OakCommandLineOption>(
OptionState{const_cast<CommandLineParser::Option *>(option)});
if (!out_option->ctx) {
return OAKCOMMON_E_NOMEM;
}
handle->option = const_cast<CommandLineParser::Option *>(option);
*out_option = handle;
}
return OAKCOMMON_OK;
@@ -109,28 +143,28 @@ int oakcommon_commandlineparser_add_option(
}
int oakcommon_commandlineparser_add_positional_argument(
OakCommonCommandLineParser *parser, const char *name,
OakCommandLineParser parser, const char *name,
const char *description, int required,
OakCommonCommandLinePositionalArgument **out_argument)
OakCommandLinePositionalArgument *out_argument)
{
if (!parser || !name) {
if (!clp(parser) || !name) {
return OAKCOMMON_E_INVALID;
}
try {
const CommandLineParser::PositionalArgument *argument =
parser->impl.add_positional_argument(
clp(parser)->add_positional_argument(
name, description ? description : "", required != 0);
if (out_argument) {
auto *handle =
new (std::nothrow) OakCommonCommandLinePositionalArgument();
if (!handle) {
*out_argument =
oakcommon::make_handle<OakCommandLinePositionalArgument>(
PositionalArgumentState{
const_cast<CommandLineParser::PositionalArgument *>(
argument)});
if (!out_argument->ctx) {
return OAKCOMMON_E_NOMEM;
}
handle->argument =
const_cast<CommandLineParser::PositionalArgument *>(argument);
*out_argument = handle;
}
return OAKCOMMON_OK;
@@ -139,10 +173,10 @@ int oakcommon_commandlineparser_add_positional_argument(
}
}
int oakcommon_commandlineparser_process(OakCommonCommandLineParser *parser,
int oakcommon_commandlineparser_process(OakCommandLineParser parser,
const char *const *argv, int argc)
{
if (!parser || !argv || argc < 0) {
if (!clp(parser) || !argv || argc < 0) {
return OAKCOMMON_E_INVALID;
}
@@ -153,37 +187,37 @@ int oakcommon_commandlineparser_process(OakCommonCommandLineParser *parser,
args.emplace_back(argv[i] ? argv[i] : "");
}
parser->impl.process(args);
clp(parser)->process(args);
return OAKCOMMON_OK;
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
int oakcommon_commandlineparser_print_help(OakCommonCommandLineParser *parser,
int oakcommon_commandlineparser_print_help(OakCommandLineParser parser,
const char *filename)
{
if (!parser || !filename) {
if (!clp(parser) || !filename) {
return OAKCOMMON_E_INVALID;
}
try {
parser->impl.print_help(filename);
clp(parser)->print_help(filename);
return OAKCOMMON_OK;
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
int oakcommon_commandlineoption_is_set(OakCommonCommandLineOption *option,
int oakcommon_commandlineoption_is_set(OakCommandLineOption option,
bool *is_set)
{
if (!option || !option->option || !is_set) {
if (!clo(option) || !is_set) {
return OAKCOMMON_E_INVALID;
}
try {
*is_set = option->option->is_set();
*is_set = clo(option)->is_set();
return OAKCOMMON_OK;
} catch (...) {
return OAKCOMMON_E_FAILED;
@@ -212,29 +246,29 @@ static int copy_setting(const std::string &value, char *buf, int buf_size)
return required;
}
int oakcommon_commandlineoption_get_setting(OakCommonCommandLineOption *option,
int oakcommon_commandlineoption_get_setting(OakCommandLineOption option,
char *buf, int buf_size)
{
if (!option || !option->option) {
if (!clo(option)) {
return OAKCOMMON_E_INVALID;
}
try {
return copy_setting(option->option->get_setting(), buf, buf_size);
return copy_setting(clo(option)->get_setting(), buf, buf_size);
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
int oakcommon_commandlineoption_set_setting(OakCommonCommandLineOption *option,
int oakcommon_commandlineoption_set_setting(OakCommandLineOption option,
const char *value)
{
if (!option || !option->option || !value) {
if (!clo(option) || !value) {
return OAKCOMMON_E_INVALID;
}
try {
option->option->set_setting(value);
clo(option)->set_setting(value);
return OAKCOMMON_OK;
} catch (...) {
return OAKCOMMON_E_FAILED;
@@ -242,41 +276,41 @@ int oakcommon_commandlineoption_set_setting(OakCommonCommandLineOption *option,
}
int oakcommon_commandlinepositionalargument_get_setting(
OakCommonCommandLinePositionalArgument *argument, char *buf, int buf_size)
OakCommandLinePositionalArgument argument, char *buf, int buf_size)
{
if (!argument || !argument->argument) {
if (!clpa(argument)) {
return OAKCOMMON_E_INVALID;
}
try {
return copy_setting(argument->argument->get_setting(), buf, buf_size);
return copy_setting(clpa(argument)->get_setting(), buf, buf_size);
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
int oakcommon_commandlinepositionalargument_set_setting(
OakCommonCommandLinePositionalArgument *argument, const char *value)
OakCommandLinePositionalArgument argument, const char *value)
{
if (!argument || !argument->argument || !value) {
if (!clpa(argument) || !value) {
return OAKCOMMON_E_INVALID;
}
try {
argument->argument->set_setting(value);
clpa(argument)->set_setting(value);
return OAKCOMMON_OK;
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
void oakcommon_commandlineoption_free(OakCommonCommandLineOption *option)
void oakcommon_commandlineoption_free(OakCommandLineOption *option)
{
delete option;
oakcommon::free_handle(option);
}
void oakcommon_commandlinepositionalargument_free(
OakCommonCommandLinePositionalArgument *argument)
OakCommandLinePositionalArgument *argument)
{
delete argument;
oakcommon::free_handle(argument);
}
+63 -41
View File
@@ -22,25 +22,47 @@
#include "../src/current.h"
struct OakCommonCurrent {
Current *current;
};
OakCommonCurrent *oakcommon_current_instance(void)
namespace
{
static OakCommonCurrent handle = { &Current::get_instance() };
return &handle;
/**
* @brief No-op addref/release for the singleton: it is never destroyed.
*/
void singleton_noop(void *ctx)
{
(void)ctx;
}
void oakcommon_current_free(OakCommonCurrent *self)
/**
* @brief Recover the Current singleton from a handle (NULL-safe).
*/
Current *current_of(OakCurrent self)
{
// No-op: the handle wraps a process-wide singleton.
return static_cast<Current *>(self.ctx);
}
} // namespace
OakCurrent oakcommon_current_instance(void)
{
OakCurrent h = {};
h.ctx = &Current::get_instance();
h.addref = &singleton_noop;
h.release = &singleton_noop;
h.abi_version = OAKCOMMON_ABI_VERSION;
return h;
}
void oakcommon_current_free(OakCurrent *self)
{
// No-op: the handle wraps a process-wide singleton whose release()
// intentionally never destroys anything.
(void)self;
}
static int current_set(Current *current,
void (Current::*set_fn)(std::shared_ptr<void>),
void *obj, OakCommonDestroyFn destroy)
void *obj, OakDestroyFn destroy)
{
try {
std::shared_ptr<void> value;
@@ -70,78 +92,78 @@ static int current_get(Current *current,
return OAKCOMMON_OK;
}
int oakcommon_current_set_video_params(OakCommonCurrent *self, void *obj,
OakCommonDestroyFn destroy)
int oakcommon_current_set_video_params(OakCurrent self, void *obj,
OakDestroyFn destroy)
{
if (!self)
if (!current_of(self))
return OAKCOMMON_E_INVALID;
return current_set(self->current, &Current::set_current_video_params,
return current_set(current_of(self), &Current::set_current_video_params,
obj, destroy);
}
int oakcommon_current_set_audio_params(OakCommonCurrent *self, void *obj,
OakCommonDestroyFn destroy)
int oakcommon_current_set_audio_params(OakCurrent self, void *obj,
OakDestroyFn destroy)
{
if (!self)
if (!current_of(self))
return OAKCOMMON_E_INVALID;
return current_set(self->current, &Current::set_current_audio_params,
return current_set(current_of(self), &Current::set_current_audio_params,
obj, destroy);
}
int oakcommon_current_set_plugin_host(OakCommonCurrent *self, void *obj,
OakCommonDestroyFn destroy)
int oakcommon_current_set_plugin_host(OakCurrent self, void *obj,
OakDestroyFn destroy)
{
if (!self)
if (!current_of(self))
return OAKCOMMON_E_INVALID;
return current_set(self->current, &Current::set_plugin_host, obj,
return current_set(current_of(self), &Current::set_plugin_host, obj,
destroy);
}
int oakcommon_current_set_plugin_cache(OakCommonCurrent *self, void *obj,
OakCommonDestroyFn destroy)
int oakcommon_current_set_plugin_cache(OakCurrent self, void *obj,
OakDestroyFn destroy)
{
if (!self)
if (!current_of(self))
return OAKCOMMON_E_INVALID;
return current_set(self->current, &Current::set_plugin_cache, obj,
return current_set(current_of(self), &Current::set_plugin_cache, obj,
destroy);
}
int oakcommon_current_get_video_params(OakCommonCurrent *self, void **out)
int oakcommon_current_get_video_params(OakCurrent self, void **out)
{
if (!self || !out)
if (!current_of(self) || !out)
return OAKCOMMON_E_INVALID;
return current_get(self->current, &Current::current_video_params,
return current_get(current_of(self), &Current::current_video_params,
out);
}
int oakcommon_current_get_audio_params(OakCommonCurrent *self, void **out)
int oakcommon_current_get_audio_params(OakCurrent self, void **out)
{
if (!self || !out)
if (!current_of(self) || !out)
return OAKCOMMON_E_INVALID;
return current_get(self->current, &Current::current_audio_params,
return current_get(current_of(self), &Current::current_audio_params,
out);
}
int oakcommon_current_get_plugin_host(OakCommonCurrent *self, void **out)
int oakcommon_current_get_plugin_host(OakCurrent self, void **out)
{
if (!self || !out)
if (!current_of(self) || !out)
return OAKCOMMON_E_INVALID;
return current_get(self->current, &Current::plugin_host, out);
return current_get(current_of(self), &Current::plugin_host, out);
}
int oakcommon_current_get_plugin_cache(OakCommonCurrent *self, void **out)
int oakcommon_current_get_plugin_cache(OakCurrent self, void **out)
{
if (!self || !out)
if (!current_of(self) || !out)
return OAKCOMMON_E_INVALID;
return current_get(self->current, &Current::plugin_cache, out);
return current_get(current_of(self), &Current::plugin_cache, out);
}
int oakcommon_current_is_interactive(OakCommonCurrent *self, int *out)
int oakcommon_current_is_interactive(OakCurrent self, int *out)
{
if (!self || !out)
if (!current_of(self) || !out)
return OAKCOMMON_E_INVALID;
try {
*out = self->current->interactive() ? 1 : 0;
*out = current_of(self)->interactive() ? 1 : 0;
} catch (...) {
return OAKCOMMON_E_FAILED;
}
+68
View File
@@ -20,6 +20,9 @@
#include "common/debug.h"
#include <cstdarg>
#include <vector>
#include "../src/debug.h"
int oakcommon_debug_log(int level, const char *msg)
@@ -43,3 +46,68 @@ int oakcommon_debug_level_name(int level, char *buf, int buf_size)
return OAKCOMMON_E_FAILED;
}
}
int oakcommon_log(int level, const char *fmt, ...)
{
if (!fmt)
return OAKCOMMON_E_INVALID;
va_list args;
va_start(args, fmt);
va_list sizing;
va_copy(sizing, args);
int needed = vsnprintf(nullptr, 0, fmt, sizing);
va_end(sizing);
if (needed < 0) {
va_end(args);
return OAKCOMMON_E_FAILED;
}
std::string msg;
try {
// Dynamically sized: arbitrary message length, no truncation,
// no fixed stack buffer.
std::vector<char> buf(static_cast<size_t>(needed) + 1);
vsnprintf(buf.data(), buf.size(), fmt, args);
msg.assign(buf.data(), static_cast<size_t>(needed));
} catch (...) {
va_end(args);
return OAKCOMMON_E_FAILED;
}
va_end(args);
try {
olive::log_message(level, msg);
} catch (...) {
return OAKCOMMON_E_FAILED;
}
return OAKCOMMON_OK;
}
int oakcommon_log_set_level(int level)
{
if (level < OAKCOMMON_DEBUG_DEBUG || level > OAKCOMMON_DEBUG_FATAL)
return OAKCOMMON_E_INVALID;
try {
olive::set_log_level(static_cast<olive::DebugLevel>(level));
} catch (...) {
return OAKCOMMON_E_FAILED;
}
return OAKCOMMON_OK;
}
int oakcommon_log_get_level(int *out_level)
{
if (!out_level)
return OAKCOMMON_E_INVALID;
try {
*out_level = static_cast<int>(olive::get_log_level());
} catch (...) {
return OAKCOMMON_E_FAILED;
}
return OAKCOMMON_OK;
}
+45 -33
View File
@@ -24,11 +24,21 @@
#include <string>
#include "../src/filefunctions.h"
#include "refcounted.h"
struct OakCommonFileFunctions {
int unused; /**< Stateless family; handle kept for API uniformity. */
namespace
{
/**
* @brief Stateless family; the boxed object is empty and only exists so
* the handle has something to reference-count.
*/
struct FileFunctionsState {
int unused;
};
} // namespace
namespace
{
@@ -54,25 +64,27 @@ bool is_valid_string_out(const char *buf, int buf_size)
} // namespace
OakCommonFileFunctions *oakcommon_filefunctions_init(void)
OakFileFunctions oakcommon_filefunctions_init(void)
{
try {
return new OakCommonFileFunctions{0};
return oakcommon::make_handle<OakFileFunctions>(
FileFunctionsState{0});
} catch (...) {
return nullptr;
OakFileFunctions h = {};
return h;
}
}
void oakcommon_filefunctions_free(OakCommonFileFunctions *self)
void oakcommon_filefunctions_free(OakFileFunctions *self)
{
delete self;
oakcommon::free_handle(self);
}
int oakcommon_filefunctions_get_unique_file_identifier(
OakCommonFileFunctions *self, const char *filename, char *buf,
OakFileFunctions self, const char *filename, char *buf,
int buf_size)
{
if (self == nullptr || filename == nullptr ||
if (self.ctx == nullptr || filename == nullptr ||
!is_valid_string_out(buf, buf_size)) {
return OAKCOMMON_E_INVALID;
}
@@ -87,9 +99,9 @@ int oakcommon_filefunctions_get_unique_file_identifier(
}
int oakcommon_filefunctions_get_configuration_location(
OakCommonFileFunctions *self, char *buf, int buf_size)
OakFileFunctions self, char *buf, int buf_size)
{
if (self == nullptr || !is_valid_string_out(buf, buf_size)) {
if (self.ctx == nullptr || !is_valid_string_out(buf, buf_size)) {
return OAKCOMMON_E_INVALID;
}
@@ -103,9 +115,9 @@ int oakcommon_filefunctions_get_configuration_location(
}
int oakcommon_filefunctions_get_application_path(
OakCommonFileFunctions *self, char *buf, int buf_size)
OakFileFunctions self, char *buf, int buf_size)
{
if (self == nullptr || !is_valid_string_out(buf, buf_size)) {
if (self.ctx == nullptr || !is_valid_string_out(buf, buf_size)) {
return OAKCOMMON_E_INVALID;
}
@@ -118,9 +130,9 @@ int oakcommon_filefunctions_get_application_path(
}
int oakcommon_filefunctions_get_temp_file_path(
OakCommonFileFunctions *self, char *buf, int buf_size)
OakFileFunctions self, char *buf, int buf_size)
{
if (self == nullptr || !is_valid_string_out(buf, buf_size)) {
if (self.ctx == nullptr || !is_valid_string_out(buf, buf_size)) {
return OAKCOMMON_E_INVALID;
}
@@ -133,9 +145,9 @@ int oakcommon_filefunctions_get_temp_file_path(
}
int oakcommon_filefunctions_get_auto_recovery_root(
OakCommonFileFunctions *self, char *buf, int buf_size)
OakFileFunctions self, char *buf, int buf_size)
{
if (self == nullptr || !is_valid_string_out(buf, buf_size)) {
if (self.ctx == nullptr || !is_valid_string_out(buf, buf_size)) {
return OAKCOMMON_E_INVALID;
}
@@ -148,10 +160,10 @@ int oakcommon_filefunctions_get_auto_recovery_root(
}
int oakcommon_filefunctions_can_copy_directory_without_overwriting(
OakCommonFileFunctions *self, const char *source, const char *dest,
OakFileFunctions self, const char *source, const char *dest,
int *out)
{
if (self == nullptr || source == nullptr || dest == nullptr ||
if (self.ctx == nullptr || source == nullptr || dest == nullptr ||
out == nullptr) {
return OAKCOMMON_E_INVALID;
}
@@ -167,11 +179,11 @@ int oakcommon_filefunctions_can_copy_directory_without_overwriting(
}
}
int oakcommon_filefunctions_copy_directory(OakCommonFileFunctions *self,
int oakcommon_filefunctions_copy_directory(OakFileFunctions self,
const char *source,
const char *dest, int overwrite)
{
if (self == nullptr || source == nullptr || dest == nullptr) {
if (self.ctx == nullptr || source == nullptr || dest == nullptr) {
return OAKCOMMON_E_INVALID;
}
@@ -184,10 +196,10 @@ int oakcommon_filefunctions_copy_directory(OakCommonFileFunctions *self,
}
int oakcommon_filefunctions_directory_is_valid(
OakCommonFileFunctions *self, const char *dir,
OakFileFunctions self, const char *dir,
int try_to_create_if_not_exists, int *out)
{
if (self == nullptr || dir == nullptr || out == nullptr) {
if (self.ctx == nullptr || dir == nullptr || out == nullptr) {
return OAKCOMMON_E_INVALID;
}
@@ -203,10 +215,10 @@ int oakcommon_filefunctions_directory_is_valid(
}
int oakcommon_filefunctions_ensure_filename_extension(
OakCommonFileFunctions *self, const char *filename,
OakFileFunctions self, const char *filename,
const char *extension, char *buf, int buf_size)
{
if (self == nullptr || filename == nullptr || extension == nullptr ||
if (self.ctx == nullptr || filename == nullptr || extension == nullptr ||
!is_valid_string_out(buf, buf_size)) {
return OAKCOMMON_E_INVALID;
}
@@ -222,10 +234,10 @@ int oakcommon_filefunctions_ensure_filename_extension(
}
int oakcommon_filefunctions_read_file_as_string(
OakCommonFileFunctions *self, const char *filename, char *buf,
OakFileFunctions self, const char *filename, char *buf,
int buf_size)
{
if (self == nullptr || filename == nullptr ||
if (self.ctx == nullptr || filename == nullptr ||
!is_valid_string_out(buf, buf_size)) {
return OAKCOMMON_E_INVALID;
}
@@ -240,10 +252,10 @@ int oakcommon_filefunctions_read_file_as_string(
}
int oakcommon_filefunctions_get_safe_temporary_filename(
OakCommonFileFunctions *self, const char *original, char *buf,
OakFileFunctions self, const char *original, char *buf,
int buf_size)
{
if (self == nullptr || original == nullptr ||
if (self.ctx == nullptr || original == nullptr ||
!is_valid_string_out(buf, buf_size)) {
return OAKCOMMON_E_INVALID;
}
@@ -258,10 +270,10 @@ int oakcommon_filefunctions_get_safe_temporary_filename(
}
int oakcommon_filefunctions_rename_file_allow_overwrite(
OakCommonFileFunctions *self, const char *from, const char *to,
OakFileFunctions self, const char *from, const char *to,
int *out)
{
if (self == nullptr || from == nullptr || to == nullptr ||
if (self.ctx == nullptr || from == nullptr || to == nullptr ||
out == nullptr) {
return OAKCOMMON_E_INVALID;
}
@@ -276,10 +288,10 @@ int oakcommon_filefunctions_rename_file_allow_overwrite(
}
int oakcommon_filefunctions_get_formatted_executable_for_platform(
OakCommonFileFunctions *self, const char *unformatted, char *buf,
OakFileFunctions self, const char *unformatted, char *buf,
int buf_size)
{
if (self == nullptr || unformatted == nullptr ||
if (self.ctx == nullptr || unformatted == nullptr ||
!is_valid_string_out(buf, buf_size)) {
return OAKCOMMON_E_INVALID;
}
+21 -9
View File
@@ -23,30 +23,42 @@
#include <new>
#include "../src/ocioutils.h"
#include "refcounted.h"
struct OakCommonOCIOUtils {
int unused; /**< Stateless; only the address matters. */
namespace
{
/**
* @brief Stateless family; the boxed object is empty and only exists so
* the handle has something to reference-count.
*/
struct OCIOUtilsState {
int unused;
};
OakCommonOCIOUtils *oakcommon_ocioutils_init(void)
} // namespace
OakOCIOUtils oakcommon_ocioutils_init(void)
{
try {
return new (std::nothrow) OakCommonOCIOUtils{};
return oakcommon::make_handle<OakOCIOUtils>(
OCIOUtilsState{0});
} catch (...) {
return NULL;
OakOCIOUtils h = {};
return h;
}
}
void oakcommon_ocioutils_free(OakCommonOCIOUtils *self)
void oakcommon_ocioutils_free(OakOCIOUtils *self)
{
delete self;
oakcommon::free_handle(self);
}
int oakcommon_ocioutils_get_ocio_bit_depth_from_pixel_format(
OakCommonOCIOUtils *self, int pixel_format, int *out_bit_depth)
OakOCIOUtils self, int pixel_format, int *out_bit_depth)
{
try {
if (self == NULL || out_bit_depth == NULL)
if (self.ctx == NULL || out_bit_depth == NULL)
return OAKCOMMON_E_INVALID;
if (pixel_format < OAKCOMMON_PIXEL_FORMAT_INVALID ||
pixel_format >= OAKCOMMON_PIXEL_FORMAT_COUNT)
+25 -13
View File
@@ -23,30 +23,42 @@
#include <new>
#include "../src/oiioutils.h"
#include "refcounted.h"
struct OakCommonOIIOUtils {
int unused; /**< Stateless; only the address matters. */
namespace
{
/**
* @brief Stateless family; the boxed object is empty and only exists so
* the handle has something to reference-count.
*/
struct OIIOUtilsState {
int unused;
};
OakCommonOIIOUtils *oakcommon_oiioutils_init(void)
} // namespace
OakOIIOUtils oakcommon_oiioutils_init(void)
{
try {
return new (std::nothrow) OakCommonOIIOUtils{};
return oakcommon::make_handle<OakOIIOUtils>(
OIIOUtilsState{0});
} catch (...) {
return NULL;
OakOIIOUtils h = {};
return h;
}
}
void oakcommon_oiioutils_free(OakCommonOIIOUtils *self)
void oakcommon_oiioutils_free(OakOIIOUtils *self)
{
delete self;
oakcommon::free_handle(self);
}
int oakcommon_oiioutils_get_oiio_base_type_from_format(
OakCommonOIIOUtils *self, int pixel_format, int *out_base_type)
OakOIIOUtils self, int pixel_format, int *out_base_type)
{
try {
if (self == NULL || out_base_type == NULL)
if (self.ctx == NULL || out_base_type == NULL)
return OAKCOMMON_E_INVALID;
if (pixel_format < OAKCOMMON_PIXEL_FORMAT_INVALID ||
pixel_format >= OAKCOMMON_PIXEL_FORMAT_COUNT)
@@ -63,10 +75,10 @@ int oakcommon_oiioutils_get_oiio_base_type_from_format(
}
int oakcommon_oiioutils_get_format_from_oiio_basetype(
OakCommonOIIOUtils *self, int base_type, int *out_pixel_format)
OakOIIOUtils self, int base_type, int *out_pixel_format)
{
try {
if (self == NULL || out_pixel_format == NULL)
if (self.ctx == NULL || out_pixel_format == NULL)
return OAKCOMMON_E_INVALID;
if (base_type < 0 || base_type >= OIIO::TypeDesc::LASTBASE)
return OAKCOMMON_E_INVALID;
@@ -82,11 +94,11 @@ int oakcommon_oiioutils_get_format_from_oiio_basetype(
}
int oakcommon_oiioutils_get_pixel_aspect_ratio(
OakCommonOIIOUtils *self, double pixel_aspect_ratio, int *out_numerator,
OakOIIOUtils self, double pixel_aspect_ratio, int *out_numerator,
int *out_denominator)
{
try {
if (self == NULL || out_numerator == NULL || out_denominator == NULL)
if (self.ctx == NULL || out_numerator == NULL || out_denominator == NULL)
return OAKCOMMON_E_INVALID;
olive::core::Rational par =
+132
View File
@@ -0,0 +1,132 @@
/***
Oak Video Editor - 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 OAKCOMMON_C_API_REFCOUNTED_H
#define OAKCOMMON_C_API_REFCOUNTED_H
#include <atomic>
#include <cstdint>
#include <type_traits>
#include <utility>
#include "common/handle.h"
namespace oakcommon
{
/**
* @brief Heap box behind every handle's ctx pointer.
*
* Holds the wrapped object plus its atomic reference count. addref and
* release are emitted per boxed type so that the function pointers stored
* in a handle always run code from the DLL that created the object.
*/
template <typename T> struct RefCounted {
T impl;
std::atomic<uint32_t> refs;
template <typename... Args>
explicit RefCounted(Args &&...args)
: impl(std::forward<Args>(args)...)
, refs(1)
{
}
};
/**
* @brief Handle addref thunk: atomically increments the count.
*/
template <typename T> void ref_counted_addref(void *ctx)
{
auto *box = static_cast<RefCounted<T> *>(ctx);
if (box)
box->refs.fetch_add(1, std::memory_order_relaxed);
}
/**
* @brief Handle release thunk: decrements the count, destroys at zero.
*/
template <typename T> void ref_counted_release(void *ctx)
{
auto *box = static_cast<RefCounted<T> *>(ctx);
if (box && box->refs.fetch_sub(1, std::memory_order_acq_rel) == 1)
delete box;
}
/**
* @brief Build a by-value handle owning a freshly boxed object (count 1).
*
* The object is constructed in place inside the box, so non-movable
* types are supported. On allocation failure the returned handle has
* ctx == NULL (all C API functions treat that as OAKCOMMON_E_INVALID
* and free() as a no-op).
*/
template <typename Handle, typename T, typename... Args>
Handle make_handle_in_place(Args &&...args)
{
Handle h = {};
try {
h.ctx = new RefCounted<T>(std::forward<Args>(args)...);
} catch (...) {
h.ctx = nullptr;
}
h.addref = &ref_counted_addref<T>;
h.release = &ref_counted_release<T>;
h.abi_version = OAKCOMMON_ABI_VERSION;
return h;
}
/**
* @brief Build a by-value handle from an existing object (copied/moved
* into the box, reference count 1).
*/
template <typename Handle, typename T>
Handle make_handle(T &&value)
{
return make_handle_in_place<Handle, typename std::decay<T>::type>(
std::forward<T>(value));
}
/**
* @brief Recover the boxed object from a handle ctx (NULL-safe).
*/
template <typename T> T *handle_impl(void *ctx)
{
auto *box = static_cast<RefCounted<T> *>(ctx);
return box ? &box->impl : nullptr;
}
/**
* @brief Shared free() body: release the ctx, no-op on NULL/empty handle.
*
* Clears ctx afterwards so a double free through the same (copied)
* struct is caught by the caller's own bookkeeping, not by us.
*/
template <typename Handle> void free_handle(Handle *h)
{
if (!h || !h->ctx || !h->release)
return;
h->release(h->ctx);
h->ctx = nullptr;
}
} // namespace oakcommon
#endif // OAKCOMMON_C_API_REFCOUNTED_H
+73 -50
View File
@@ -23,14 +23,19 @@
#include <cstring>
#include "../src/subtitleparams.h"
struct OakCommonSubtitleParams {
olive::SubtitleParams impl;
};
#include "refcounted.h"
namespace
{
/**
* @brief Recover the boxed olive::SubtitleParams from a handle (NULL-safe).
*/
olive::SubtitleParams *sp(OakSubtitleParams params)
{
return oakcommon::handle_impl<olive::SubtitleParams>(params.ctx);
}
int copy_string(const std::string &value, char *buf, int buf_size)
{
int needed = (int)value.size() + 1;
@@ -41,92 +46,110 @@ int copy_string(const std::string &value, char *buf, int buf_size)
} // namespace
OakCommonSubtitleParams *oakcommon_subtitleparams_init(void)
OakSubtitleParams oakcommon_subtitleparams_init(void)
{
try {
return new OakCommonSubtitleParams{olive::SubtitleParams()};
return oakcommon::make_handle<OakSubtitleParams>(
olive::SubtitleParams());
} catch (...) {
return nullptr;
OakSubtitleParams h = {};
return h;
}
}
void oakcommon_subtitleparams_free(OakCommonSubtitleParams *params)
OakSubtitleParams oakcommon_subtitleparams_init_from_native(
const olive::SubtitleParams *src)
{
delete params;
if (!src) {
OakSubtitleParams h = {};
return h;
}
try {
return oakcommon::make_handle<OakSubtitleParams>(
olive::SubtitleParams(*src));
} catch (...) {
OakSubtitleParams h = {};
return h;
}
}
void oakcommon_subtitleparams_free(OakSubtitleParams *params)
{
oakcommon::free_handle(params);
}
int oakcommon_subtitleparams_get_stream_index(
OakCommonSubtitleParams *params, int *index)
OakSubtitleParams params, int *index)
{
if (!params || !index)
if (!sp(params) || !index)
return OAKCOMMON_E_INVALID;
*index = params->impl.stream_index();
*index = sp(params)->stream_index();
return OAKCOMMON_OK;
}
int oakcommon_subtitleparams_set_stream_index(
OakCommonSubtitleParams *params, int index)
OakSubtitleParams params, int index)
{
if (!params)
if (!sp(params))
return OAKCOMMON_E_INVALID;
params->impl.set_stream_index(index);
sp(params)->set_stream_index(index);
return OAKCOMMON_OK;
}
int oakcommon_subtitleparams_get_enabled(OakCommonSubtitleParams *params,
int oakcommon_subtitleparams_get_enabled(OakSubtitleParams params,
int *enabled)
{
if (!params || !enabled)
if (!sp(params) || !enabled)
return OAKCOMMON_E_INVALID;
*enabled = params->impl.enabled() ? 1 : 0;
*enabled = sp(params)->enabled() ? 1 : 0;
return OAKCOMMON_OK;
}
int oakcommon_subtitleparams_set_enabled(OakCommonSubtitleParams *params,
int oakcommon_subtitleparams_set_enabled(OakSubtitleParams params,
int enabled)
{
if (!params)
if (!sp(params))
return OAKCOMMON_E_INVALID;
params->impl.set_enabled(enabled != 0);
sp(params)->set_enabled(enabled != 0);
return OAKCOMMON_OK;
}
int oakcommon_subtitleparams_is_valid(OakCommonSubtitleParams *params,
int oakcommon_subtitleparams_is_valid(OakSubtitleParams params,
int *is_valid)
{
if (!params || !is_valid)
if (!sp(params) || !is_valid)
return OAKCOMMON_E_INVALID;
*is_valid = params->impl.is_valid() ? 1 : 0;
*is_valid = sp(params)->is_valid() ? 1 : 0;
return OAKCOMMON_OK;
}
int oakcommon_subtitleparams_count(OakCommonSubtitleParams *params, int *count)
int oakcommon_subtitleparams_count(OakSubtitleParams params, int *count)
{
if (!params || !count)
if (!sp(params) || !count)
return OAKCOMMON_E_INVALID;
*count = (int)params->impl.size();
*count = (int)sp(params)->size();
return OAKCOMMON_OK;
}
int oakcommon_subtitleparams_duration(OakCommonSubtitleParams *params,
int oakcommon_subtitleparams_duration(OakSubtitleParams params,
int *numerator, int *denominator)
{
if (!params || !numerator || !denominator)
if (!sp(params) || !numerator || !denominator)
return OAKCOMMON_E_INVALID;
olive::core::Rational d = params->impl.duration();
olive::core::Rational d = sp(params)->duration();
*numerator = d.numerator();
*denominator = d.denominator();
return OAKCOMMON_OK;
}
int oakcommon_subtitleparams_add_subtitle(OakCommonSubtitleParams *params,
int oakcommon_subtitleparams_add_subtitle(OakSubtitleParams params,
int in_num, int in_den, int out_num,
int out_den, const char *text)
{
if (!params || !text)
if (!sp(params) || !text)
return OAKCOMMON_E_INVALID;
try {
params->impl.push_back(olive::Subtitle(
sp(params)->push_back(olive::Subtitle(
olive::core::TimeRange(olive::core::Rational(in_num, in_den),
olive::core::Rational(out_num, out_den)),
text));
@@ -136,23 +159,23 @@ int oakcommon_subtitleparams_add_subtitle(OakCommonSubtitleParams *params,
}
}
int oakcommon_subtitleparams_clear(OakCommonSubtitleParams *params)
int oakcommon_subtitleparams_clear(OakSubtitleParams params)
{
if (!params)
if (!sp(params))
return OAKCOMMON_E_INVALID;
params->impl.clear();
sp(params)->clear();
return OAKCOMMON_OK;
}
int oakcommon_subtitleparams_get_subtitle(OakCommonSubtitleParams *params,
int oakcommon_subtitleparams_get_subtitle(OakSubtitleParams params,
int index, int *in_num, int *in_den,
int *out_num, int *out_den)
{
if (!params || !in_num || !in_den || !out_num || !out_den)
if (!sp(params) || !in_num || !in_den || !out_num || !out_den)
return OAKCOMMON_E_INVALID;
if (index < 0 || index >= (int)params->impl.size())
if (index < 0 || index >= (int)sp(params)->size())
return OAKCOMMON_E_NOT_FOUND;
const olive::Subtitle &s = params->impl.at(index);
const olive::Subtitle &s = sp(params)->at(index);
olive::core::Rational in = s.time().in();
olive::core::Rational out = s.time().out();
*in_num = in.numerator();
@@ -162,16 +185,16 @@ int oakcommon_subtitleparams_get_subtitle(OakCommonSubtitleParams *params,
return OAKCOMMON_OK;
}
int oakcommon_subtitleparams_get_subtitle_text(OakCommonSubtitleParams *params,
int oakcommon_subtitleparams_get_subtitle_text(OakSubtitleParams params,
int index, char *buf,
int buf_size)
{
if (!params)
if (!sp(params))
return OAKCOMMON_E_INVALID;
if (index < 0 || index >= (int)params->impl.size())
if (index < 0 || index >= (int)sp(params)->size())
return OAKCOMMON_E_NOT_FOUND;
try {
return copy_string(params->impl.at(index).text(), buf, buf_size);
return copy_string(sp(params)->at(index).text(), buf, buf_size);
} catch (...) {
return OAKCOMMON_E_FAILED;
}
@@ -187,10 +210,10 @@ int oakcommon_subtitleparams_generate_ass_header(char *buf, int buf_size)
}
}
int oakcommon_subtitleparams_load_xml(OakCommonSubtitleParams *params,
int oakcommon_subtitleparams_load_xml(OakSubtitleParams params,
const char *xml)
{
if (!params || !xml)
if (!sp(params) || !xml)
return OAKCOMMON_E_INVALID;
try {
olive::XmlStreamReader reader(xml);
@@ -199,22 +222,22 @@ int oakcommon_subtitleparams_load_xml(OakCommonSubtitleParams *params,
// Position on the root element; load() consumes its children.
if (!olive::xml_read_next_start_element(&reader))
return OAKCOMMON_E_FAILED;
params->impl.load(&reader);
sp(params)->load(&reader);
return OAKCOMMON_OK;
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
int oakcommon_subtitleparams_save_xml(OakCommonSubtitleParams *params,
int oakcommon_subtitleparams_save_xml(OakSubtitleParams params,
char *buf, int buf_size)
{
if (!params)
if (!sp(params))
return OAKCOMMON_E_INVALID;
try {
olive::XmlStreamWriter writer;
writer.write_start_element("subtitleparams");
params->impl.save(&writer);
sp(params)->save(&writer);
writer.write_end_element();
return copy_string(writer.output(), buf, buf_size);
} catch (...) {
+200 -132
View File
@@ -23,10 +23,20 @@
#include <cstring>
#include "../src/videoparams.h"
#include "refcounted.h"
struct OakCommonVideoParams {
olive::VideoParams impl;
};
namespace
{
/**
* @brief Recover the boxed olive::VideoParams from a handle (NULL-safe).
*/
olive::VideoParams *vp(OakVideoParams params)
{
return oakcommon::handle_impl<olive::VideoParams>(params.ctx);
}
} // namespace
namespace
{
@@ -51,317 +61,345 @@ int get_rational(const olive::core::Rational &r, int *numerator,
} // namespace
OakCommonVideoParams *oakcommon_videoparams_init(void)
OakVideoParams oakcommon_videoparams_init(void)
{
try {
return new OakCommonVideoParams{olive::VideoParams()};
return oakcommon::make_handle<OakVideoParams>(
olive::VideoParams());
} catch (...) {
return nullptr;
OakVideoParams h = {};
return h;
}
}
OakCommonVideoParams *oakcommon_videoparams_init_basic(
OakVideoParams oakcommon_videoparams_init_basic(
int width, int height, int pixel_format, int nb_channels,
int pixel_aspect_num, int pixel_aspect_den, int interlacing, int divider)
{
try {
return new OakCommonVideoParams{olive::VideoParams(
width, height,
static_cast<olive::core::PixelFormat::Format>(pixel_format),
nb_channels,
olive::core::Rational(pixel_aspect_num, pixel_aspect_den),
static_cast<olive::VideoParams::Interlacing>(interlacing),
divider)};
return oakcommon::make_handle<OakVideoParams>(
olive::VideoParams(
width, height,
static_cast<olive::core::PixelFormat::Format>(pixel_format),
nb_channels,
olive::core::Rational(pixel_aspect_num, pixel_aspect_den),
static_cast<olive::VideoParams::Interlacing>(interlacing),
divider));
} catch (...) {
return nullptr;
OakVideoParams h = {};
return h;
}
}
OakCommonVideoParams *oakcommon_videoparams_init_with_time_base(
OakVideoParams oakcommon_videoparams_init_with_time_base(
int width, int height, int time_base_num, int time_base_den,
int pixel_format, int nb_channels, int pixel_aspect_num,
int pixel_aspect_den, int interlacing, int divider)
{
try {
return new OakCommonVideoParams{olive::VideoParams(
width, height,
olive::core::Rational(time_base_num, time_base_den),
static_cast<olive::core::PixelFormat::Format>(pixel_format),
nb_channels,
olive::core::Rational(pixel_aspect_num, pixel_aspect_den),
static_cast<olive::VideoParams::Interlacing>(interlacing),
divider)};
return oakcommon::make_handle<OakVideoParams>(
olive::VideoParams(
width, height,
olive::core::Rational(time_base_num, time_base_den),
static_cast<olive::core::PixelFormat::Format>(pixel_format),
nb_channels,
olive::core::Rational(pixel_aspect_num, pixel_aspect_den),
static_cast<olive::VideoParams::Interlacing>(interlacing),
divider));
} catch (...) {
return nullptr;
OakVideoParams h = {};
return h;
}
}
void oakcommon_videoparams_free(OakCommonVideoParams *params)
OakVideoParams oakcommon_videoparams_init_from_native(
const olive::VideoParams *src)
{
delete params;
if (!src) {
OakVideoParams h = {};
return h;
}
try {
return oakcommon::make_handle<OakVideoParams>(
olive::VideoParams(*src));
} catch (...) {
OakVideoParams h = {};
return h;
}
}
const olive::VideoParams *oakcommon_videoparams_get_native(
OakVideoParams params)
{
return vp(params);
}
void oakcommon_videoparams_free(OakVideoParams *params)
{
oakcommon::free_handle(params);
}
#define OAKCOMMON_VIDEOPARAMS_INT_GETTER(name, expr) \
int oakcommon_videoparams_get_##name(OakCommonVideoParams *params, \
int oakcommon_videoparams_get_##name(OakVideoParams params, \
int *out) \
{ \
if (!params || !out) \
if (!vp(params) || !out) \
return OAKCOMMON_E_INVALID; \
*out = (expr); \
return OAKCOMMON_OK; \
}
#define OAKCOMMON_VIDEOPARAMS_INT_SETTER(name, stmt) \
int oakcommon_videoparams_set_##name(OakCommonVideoParams *params, \
int oakcommon_videoparams_set_##name(OakVideoParams params, \
int value) \
{ \
if (!params) \
if (!vp(params)) \
return OAKCOMMON_E_INVALID; \
stmt; \
return OAKCOMMON_OK; \
}
OAKCOMMON_VIDEOPARAMS_INT_GETTER(width, params->impl.width())
OAKCOMMON_VIDEOPARAMS_INT_SETTER(width, params->impl.set_width(value))
OAKCOMMON_VIDEOPARAMS_INT_GETTER(height, params->impl.height())
OAKCOMMON_VIDEOPARAMS_INT_SETTER(height, params->impl.set_height(value))
OAKCOMMON_VIDEOPARAMS_INT_GETTER(depth, params->impl.depth())
OAKCOMMON_VIDEOPARAMS_INT_SETTER(depth, params->impl.set_depth(value))
OAKCOMMON_VIDEOPARAMS_INT_GETTER(is_3d, params->impl.is_3d() ? 1 : 0)
OAKCOMMON_VIDEOPARAMS_INT_GETTER(format, static_cast<int>(params->impl.format()))
OAKCOMMON_VIDEOPARAMS_INT_GETTER(width, vp(params)->width())
OAKCOMMON_VIDEOPARAMS_INT_SETTER(width, vp(params)->set_width(value))
OAKCOMMON_VIDEOPARAMS_INT_GETTER(height, vp(params)->height())
OAKCOMMON_VIDEOPARAMS_INT_SETTER(height, vp(params)->set_height(value))
OAKCOMMON_VIDEOPARAMS_INT_GETTER(depth, vp(params)->depth())
OAKCOMMON_VIDEOPARAMS_INT_SETTER(depth, vp(params)->set_depth(value))
OAKCOMMON_VIDEOPARAMS_INT_GETTER(is_3d, vp(params)->is_3d() ? 1 : 0)
OAKCOMMON_VIDEOPARAMS_INT_GETTER(format, static_cast<int>(vp(params)->format()))
OAKCOMMON_VIDEOPARAMS_INT_SETTER(
format, params->impl.set_format(
format, vp(params)->set_format(
static_cast<olive::core::PixelFormat::Format>(value)))
OAKCOMMON_VIDEOPARAMS_INT_GETTER(channel_count, params->impl.channel_count())
OAKCOMMON_VIDEOPARAMS_INT_GETTER(channel_count, vp(params)->channel_count())
OAKCOMMON_VIDEOPARAMS_INT_SETTER(channel_count,
params->impl.set_channel_count(value))
vp(params)->set_channel_count(value))
OAKCOMMON_VIDEOPARAMS_INT_GETTER(interlacing,
static_cast<int>(params->impl.interlacing()))
static_cast<int>(vp(params)->interlacing()))
OAKCOMMON_VIDEOPARAMS_INT_SETTER(
interlacing,
params->impl.set_interlacing(
vp(params)->set_interlacing(
static_cast<olive::VideoParams::Interlacing>(value)))
OAKCOMMON_VIDEOPARAMS_INT_GETTER(divider, params->impl.divider())
OAKCOMMON_VIDEOPARAMS_INT_SETTER(divider, params->impl.set_divider(value))
OAKCOMMON_VIDEOPARAMS_INT_GETTER(enabled, params->impl.enabled() ? 1 : 0)
OAKCOMMON_VIDEOPARAMS_INT_SETTER(enabled, params->impl.set_enabled(value != 0))
OAKCOMMON_VIDEOPARAMS_INT_GETTER(stream_index, params->impl.stream_index())
OAKCOMMON_VIDEOPARAMS_INT_GETTER(divider, vp(params)->divider())
OAKCOMMON_VIDEOPARAMS_INT_SETTER(divider, vp(params)->set_divider(value))
OAKCOMMON_VIDEOPARAMS_INT_GETTER(enabled, vp(params)->enabled() ? 1 : 0)
OAKCOMMON_VIDEOPARAMS_INT_SETTER(enabled, vp(params)->set_enabled(value != 0))
OAKCOMMON_VIDEOPARAMS_INT_GETTER(stream_index, vp(params)->stream_index())
OAKCOMMON_VIDEOPARAMS_INT_SETTER(stream_index,
params->impl.set_stream_index(value))
vp(params)->set_stream_index(value))
OAKCOMMON_VIDEOPARAMS_INT_GETTER(video_type,
static_cast<int>(params->impl.video_type()))
static_cast<int>(vp(params)->video_type()))
OAKCOMMON_VIDEOPARAMS_INT_SETTER(
video_type,
params->impl.set_video_type(static_cast<olive::VideoParams::Type>(value)))
vp(params)->set_video_type(static_cast<olive::VideoParams::Type>(value)))
OAKCOMMON_VIDEOPARAMS_INT_GETTER(premultiplied_alpha,
params->impl.premultiplied_alpha() ? 1 : 0)
vp(params)->premultiplied_alpha() ? 1 : 0)
OAKCOMMON_VIDEOPARAMS_INT_SETTER(premultiplied_alpha,
params->impl.set_premultiplied_alpha(
vp(params)->set_premultiplied_alpha(
value != 0))
OAKCOMMON_VIDEOPARAMS_INT_GETTER(color_range,
static_cast<int>(params->impl.color_range()))
static_cast<int>(vp(params)->color_range()))
OAKCOMMON_VIDEOPARAMS_INT_SETTER(
color_range, params->impl.set_color_range(
color_range, vp(params)->set_color_range(
static_cast<olive::VideoParams::ColorRange>(value)))
OAKCOMMON_VIDEOPARAMS_INT_GETTER(color_primaries,
params->impl.color_primaries())
vp(params)->color_primaries())
OAKCOMMON_VIDEOPARAMS_INT_SETTER(color_primaries,
params->impl.set_color_primaries(value))
OAKCOMMON_VIDEOPARAMS_INT_GETTER(color_transfer, params->impl.color_transfer())
vp(params)->set_color_primaries(value))
OAKCOMMON_VIDEOPARAMS_INT_GETTER(color_transfer, vp(params)->color_transfer())
OAKCOMMON_VIDEOPARAMS_INT_SETTER(color_transfer,
params->impl.set_color_transfer(value))
vp(params)->set_color_transfer(value))
OAKCOMMON_VIDEOPARAMS_INT_GETTER(square_pixel_width,
params->impl.square_pixel_width())
vp(params)->square_pixel_width())
OAKCOMMON_VIDEOPARAMS_INT_GETTER(effective_width,
params->impl.effective_width())
vp(params)->effective_width())
OAKCOMMON_VIDEOPARAMS_INT_GETTER(effective_height,
params->impl.effective_height())
vp(params)->effective_height())
OAKCOMMON_VIDEOPARAMS_INT_GETTER(effective_depth,
params->impl.effective_depth())
OAKCOMMON_VIDEOPARAMS_INT_GETTER(is_valid, params->impl.is_valid() ? 1 : 0)
vp(params)->effective_depth())
OAKCOMMON_VIDEOPARAMS_INT_GETTER(is_valid, vp(params)->is_valid() ? 1 : 0)
OAKCOMMON_VIDEOPARAMS_INT_GETTER(bytes_per_channel,
params->impl.get_bytes_per_channel())
vp(params)->get_bytes_per_channel())
OAKCOMMON_VIDEOPARAMS_INT_GETTER(bytes_per_pixel,
params->impl.get_bytes_per_pixel())
OAKCOMMON_VIDEOPARAMS_INT_GETTER(buffer_size, params->impl.get_buffer_size())
vp(params)->get_bytes_per_pixel())
OAKCOMMON_VIDEOPARAMS_INT_GETTER(buffer_size, vp(params)->get_buffer_size())
int oakcommon_videoparams_get_x(OakCommonVideoParams *params, float *x)
int oakcommon_videoparams_get_x(OakVideoParams params, float *x)
{
if (!params || !x)
if (!vp(params) || !x)
return OAKCOMMON_E_INVALID;
*x = params->impl.x();
*x = vp(params)->x();
return OAKCOMMON_OK;
}
int oakcommon_videoparams_set_x(OakCommonVideoParams *params, float x)
int oakcommon_videoparams_set_x(OakVideoParams params, float x)
{
if (!params)
if (!vp(params))
return OAKCOMMON_E_INVALID;
params->impl.set_x(x);
vp(params)->set_x(x);
return OAKCOMMON_OK;
}
int oakcommon_videoparams_get_y(OakCommonVideoParams *params, float *y)
int oakcommon_videoparams_get_y(OakVideoParams params, float *y)
{
if (!params || !y)
if (!vp(params) || !y)
return OAKCOMMON_E_INVALID;
*y = params->impl.y();
*y = vp(params)->y();
return OAKCOMMON_OK;
}
int oakcommon_videoparams_set_y(OakCommonVideoParams *params, float y)
int oakcommon_videoparams_set_y(OakVideoParams params, float y)
{
if (!params)
if (!vp(params))
return OAKCOMMON_E_INVALID;
params->impl.set_y(y);
vp(params)->set_y(y);
return OAKCOMMON_OK;
}
int oakcommon_videoparams_get_start_time(OakCommonVideoParams *params,
int oakcommon_videoparams_get_start_time(OakVideoParams params,
int64_t *start_time)
{
if (!params || !start_time)
if (!vp(params) || !start_time)
return OAKCOMMON_E_INVALID;
*start_time = params->impl.start_time();
*start_time = vp(params)->start_time();
return OAKCOMMON_OK;
}
int oakcommon_videoparams_set_start_time(OakCommonVideoParams *params,
int oakcommon_videoparams_set_start_time(OakVideoParams params,
int64_t start_time)
{
if (!params)
if (!vp(params))
return OAKCOMMON_E_INVALID;
params->impl.set_start_time(start_time);
vp(params)->set_start_time(start_time);
return OAKCOMMON_OK;
}
int oakcommon_videoparams_get_duration(OakCommonVideoParams *params,
int oakcommon_videoparams_get_duration(OakVideoParams params,
int64_t *duration)
{
if (!params || !duration)
if (!vp(params) || !duration)
return OAKCOMMON_E_INVALID;
*duration = params->impl.duration();
*duration = vp(params)->duration();
return OAKCOMMON_OK;
}
int oakcommon_videoparams_set_duration(OakCommonVideoParams *params,
int oakcommon_videoparams_set_duration(OakVideoParams params,
int64_t duration)
{
if (!params)
if (!vp(params))
return OAKCOMMON_E_INVALID;
params->impl.set_duration(duration);
vp(params)->set_duration(duration);
return OAKCOMMON_OK;
}
int oakcommon_videoparams_get_time_base(OakCommonVideoParams *params,
int oakcommon_videoparams_get_time_base(OakVideoParams params,
int *numerator, int *denominator)
{
if (!params)
if (!vp(params))
return OAKCOMMON_E_INVALID;
return get_rational(params->impl.time_base(), numerator, denominator);
return get_rational(vp(params)->time_base(), numerator, denominator);
}
int oakcommon_videoparams_set_time_base(OakCommonVideoParams *params,
int oakcommon_videoparams_set_time_base(OakVideoParams params,
int numerator, int denominator)
{
if (!params)
if (!vp(params))
return OAKCOMMON_E_INVALID;
params->impl.set_time_base(olive::core::Rational(numerator, denominator));
vp(params)->set_time_base(olive::core::Rational(numerator, denominator));
return OAKCOMMON_OK;
}
int oakcommon_videoparams_get_frame_rate(OakCommonVideoParams *params,
int oakcommon_videoparams_get_frame_rate(OakVideoParams params,
int *numerator, int *denominator)
{
if (!params)
if (!vp(params))
return OAKCOMMON_E_INVALID;
return get_rational(params->impl.frame_rate(), numerator, denominator);
return get_rational(vp(params)->frame_rate(), numerator, denominator);
}
int oakcommon_videoparams_set_frame_rate(OakCommonVideoParams *params,
int oakcommon_videoparams_set_frame_rate(OakVideoParams params,
int numerator, int denominator)
{
if (!params)
if (!vp(params))
return OAKCOMMON_E_INVALID;
params->impl.set_frame_rate(olive::core::Rational(numerator, denominator));
vp(params)->set_frame_rate(olive::core::Rational(numerator, denominator));
return OAKCOMMON_OK;
}
int oakcommon_videoparams_frame_rate_as_time_base(OakCommonVideoParams *params,
int oakcommon_videoparams_frame_rate_as_time_base(OakVideoParams params,
int *numerator,
int *denominator)
{
if (!params)
if (!vp(params))
return OAKCOMMON_E_INVALID;
return get_rational(params->impl.frame_rate_as_time_base(), numerator,
return get_rational(vp(params)->frame_rate_as_time_base(), numerator,
denominator);
}
int oakcommon_videoparams_get_pixel_aspect_ratio(OakCommonVideoParams *params,
int oakcommon_videoparams_get_pixel_aspect_ratio(OakVideoParams params,
int *numerator,
int *denominator)
{
if (!params)
if (!vp(params))
return OAKCOMMON_E_INVALID;
return get_rational(params->impl.pixel_aspect_ratio(), numerator,
return get_rational(vp(params)->pixel_aspect_ratio(), numerator,
denominator);
}
int oakcommon_videoparams_set_pixel_aspect_ratio(OakCommonVideoParams *params,
int oakcommon_videoparams_set_pixel_aspect_ratio(OakVideoParams params,
int numerator, int denominator)
{
if (!params)
if (!vp(params))
return OAKCOMMON_E_INVALID;
params->impl.set_pixel_aspect_ratio(
vp(params)->set_pixel_aspect_ratio(
olive::core::Rational(numerator, denominator));
return OAKCOMMON_OK;
}
int oakcommon_videoparams_get_colorspace(OakCommonVideoParams *params,
int oakcommon_videoparams_get_colorspace(OakVideoParams params,
char *buf, int buf_size)
{
if (!params)
if (!vp(params))
return OAKCOMMON_E_INVALID;
try {
return copy_string(params->impl.colorspace(), buf, buf_size);
return copy_string(vp(params)->colorspace(), buf, buf_size);
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
int oakcommon_videoparams_set_colorspace(OakCommonVideoParams *params,
int oakcommon_videoparams_set_colorspace(OakVideoParams params,
const char *colorspace)
{
if (!params || !colorspace)
if (!vp(params) || !colorspace)
return OAKCOMMON_E_INVALID;
params->impl.set_colorspace(colorspace);
vp(params)->set_colorspace(colorspace);
return OAKCOMMON_OK;
}
int oakcommon_videoparams_get_time_in_timebase_units(
OakCommonVideoParams *params, int time_num, int time_den,
OakVideoParams params, int time_num, int time_den,
int64_t *timestamp)
{
if (!params || !timestamp)
if (!vp(params) || !timestamp)
return OAKCOMMON_E_INVALID;
*timestamp = params->impl.get_time_in_timebase_units(
*timestamp = vp(params)->get_time_in_timebase_units(
olive::core::Rational(time_num, time_den));
return OAKCOMMON_OK;
}
int oakcommon_videoparams_equals(OakCommonVideoParams *params,
OakCommonVideoParams *other, int *equal)
int oakcommon_videoparams_equals(OakVideoParams params,
OakVideoParams other, int *equal)
{
if (!params || !other || !equal)
if (!vp(params) || !vp(other) || !equal)
return OAKCOMMON_E_INVALID;
*equal = (params->impl == other->impl) ? 1 : 0;
*equal = (*vp(params) == *vp(other)) ? 1 : 0;
return OAKCOMMON_OK;
}
int oakcommon_videoparams_load_xml(OakCommonVideoParams *params,
int oakcommon_videoparams_load_xml(OakVideoParams params,
const char *xml)
{
if (!params || !xml)
if (!vp(params) || !xml)
return OAKCOMMON_E_INVALID;
try {
olive::XmlStreamReader reader(xml);
@@ -370,22 +408,22 @@ int oakcommon_videoparams_load_xml(OakCommonVideoParams *params,
// Position on the root element; load() consumes its children.
if (!olive::xml_read_next_start_element(&reader))
return OAKCOMMON_E_FAILED;
params->impl.load(&reader);
vp(params)->load(&reader);
return OAKCOMMON_OK;
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
int oakcommon_videoparams_save_xml(OakCommonVideoParams *params, char *buf,
int oakcommon_videoparams_save_xml(OakVideoParams params, char *buf,
int buf_size)
{
if (!params)
if (!vp(params))
return OAKCOMMON_E_INVALID;
try {
olive::XmlStreamWriter writer;
writer.write_start_element("videoparams");
params->impl.save(&writer);
vp(params)->save(&writer);
writer.write_end_element();
return copy_string(writer.output(), buf, buf_size);
} catch (...) {
@@ -476,3 +514,33 @@ int oakcommon_videoparams_frame_rate_to_string(int numerator, int denominator,
return OAKCOMMON_E_FAILED;
}
}
static olive::PixelFormat convert_to_olive_format(OakPixelFormat format)
{
switch (format) {
case OAKCOMMON_PIXEL_FORMAT_INVALID:
return olive::PixelFormat::invalid;
case OAKCOMMON_PIXEL_FORMAT_COUNT:
return olive::PixelFormat::count;
case OAKCOMMON_PIXEL_FORMAT_U8:
return olive::PixelFormat::u8;
case OAKCOMMON_PIXEL_FORMAT_U10:
return olive::PixelFormat::u10;
case OAKCOMMON_PIXEL_FORMAT_U16:
return olive::PixelFormat::u16;
case OAKCOMMON_PIXEL_FORMAT_F16:
return olive::PixelFormat::f16;
case OAKCOMMON_PIXEL_FORMAT_F32:
return olive::PixelFormat::f32;
}
return olive::PixelFormat::invalid;
}
int oakcommon_videoparams_static_get_bytes_per_channel(OakPixelFormat format)
{
return olive::VideoParams::get_bytes_per_channel(convert_to_olive_format(format));
}
int oakcommon_videoparams_static_get_bytes_per_pixel(OakPixelFormat format, int channels)
{
return olive::VideoParams::get_bytes_per_pixel(convert_to_olive_format(format), channels);
}
+89 -68
View File
@@ -24,24 +24,40 @@
#include <new>
#include "../src/xmlutils.h"
#include "refcounted.h"
struct OakCommonXmlReader {
namespace
{
/**
* @brief Reader state boxed behind the handle's ctx pointer.
*/
struct XmlReaderState {
olive::XmlStreamReader reader;
std::string cached_text;
bool has_cached_text = false;
explicit OakCommonXmlReader(const char *data)
explicit XmlReaderState(const char *data)
: reader(data)
{
}
};
struct OakCommonXmlWriter {
olive::XmlStreamWriter writer;
};
namespace
/**
* @brief Recover the boxed reader state from a handle (NULL-safe).
*/
XmlReaderState *xr(OakXmlReader reader)
{
return oakcommon::handle_impl<XmlReaderState>(reader.ctx);
}
/**
* @brief Recover the boxed writer from a handle (NULL-safe).
*/
olive::XmlStreamWriter *xw(OakXmlWriter writer)
{
return oakcommon::handle_impl<olive::XmlStreamWriter>(writer.ctx);
}
/**
* @brief Copy @p value into the two-stage string buffer.
@@ -62,99 +78,102 @@ int copy_string(const std::string &value, char *buf, int buf_size)
extern "C" {
OakCommonXmlReader *oakcommon_xml_reader_init(const char *data)
OakXmlReader oakcommon_xml_reader_init(const char *data)
{
OakXmlReader h = {};
if (!data)
return nullptr;
return h;
try {
return new (std::nothrow) OakCommonXmlReader(data);
return oakcommon::make_handle<OakXmlReader>(
XmlReaderState(data));
} catch (...) {
return nullptr;
OakXmlReader empty = {};
return empty;
}
}
void oakcommon_xml_reader_free(OakCommonXmlReader *reader)
void oakcommon_xml_reader_free(OakXmlReader *reader)
{
delete reader;
oakcommon::free_handle(reader);
}
int oakcommon_xml_reader_read_next_start_element(OakCommonXmlReader *reader,
int oakcommon_xml_reader_read_next_start_element(OakXmlReader reader,
int *found)
{
if (!reader || !found)
if (!xr(reader) || !found)
return OAKCOMMON_E_INVALID;
try {
reader->has_cached_text = false;
*found = olive::xml_read_next_start_element(&reader->reader) ? 1 : 0;
xr(reader)->has_cached_text = false;
*found = olive::xml_read_next_start_element(&xr(reader)->reader) ? 1 : 0;
return OAKCOMMON_OK;
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
int oakcommon_xml_reader_name(OakCommonXmlReader *reader, char *buf,
int oakcommon_xml_reader_name(OakXmlReader reader, char *buf,
int buf_size)
{
if (!reader)
if (!xr(reader))
return OAKCOMMON_E_INVALID;
try {
return copy_string(reader->reader.name(), buf, buf_size);
return copy_string(xr(reader)->reader.name(), buf, buf_size);
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
int oakcommon_xml_reader_read_element_text(OakCommonXmlReader *reader,
int oakcommon_xml_reader_read_element_text(OakXmlReader reader,
char *buf, int buf_size)
{
if (!reader)
if (!xr(reader))
return OAKCOMMON_E_INVALID;
try {
// read_element_text() consumes the stream, so cache the result to
// keep the two-stage (size query then copy) buffer convention working.
if (!reader->has_cached_text) {
reader->cached_text = reader->reader.read_element_text();
reader->has_cached_text = true;
if (!xr(reader)->has_cached_text) {
xr(reader)->cached_text = xr(reader)->reader.read_element_text();
xr(reader)->has_cached_text = true;
}
return copy_string(reader->cached_text, buf, buf_size);
return copy_string(xr(reader)->cached_text, buf, buf_size);
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
int oakcommon_xml_reader_skip_current_element(OakCommonXmlReader *reader)
int oakcommon_xml_reader_skip_current_element(OakXmlReader reader)
{
if (!reader)
if (!xr(reader))
return OAKCOMMON_E_INVALID;
try {
reader->has_cached_text = false;
reader->reader.skip_current_element();
xr(reader)->has_cached_text = false;
xr(reader)->reader.skip_current_element();
return OAKCOMMON_OK;
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
int oakcommon_xml_reader_attribute_count(OakCommonXmlReader *reader,
int oakcommon_xml_reader_attribute_count(OakXmlReader reader,
int *count)
{
if (!reader || !count)
if (!xr(reader) || !count)
return OAKCOMMON_E_INVALID;
try {
*count = static_cast<int>(reader->reader.attributes().size());
*count = static_cast<int>(xr(reader)->reader.attributes().size());
return OAKCOMMON_OK;
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
int oakcommon_xml_reader_attribute_name(OakCommonXmlReader *reader, int index,
int oakcommon_xml_reader_attribute_name(OakXmlReader reader, int index,
char *buf, int buf_size)
{
if (!reader)
if (!xr(reader))
return OAKCOMMON_E_INVALID;
try {
const auto &attrs = reader->reader.attributes();
const auto &attrs = xr(reader)->reader.attributes();
if (index < 0 || index >= static_cast<int>(attrs.size()))
return OAKCOMMON_E_NOT_FOUND;
return copy_string(attrs[index].name, buf, buf_size);
@@ -163,13 +182,13 @@ int oakcommon_xml_reader_attribute_name(OakCommonXmlReader *reader, int index,
}
}
int oakcommon_xml_reader_attribute_value(OakCommonXmlReader *reader,
int oakcommon_xml_reader_attribute_value(OakXmlReader reader,
int index, char *buf, int buf_size)
{
if (!reader)
if (!xr(reader))
return OAKCOMMON_E_INVALID;
try {
const auto &attrs = reader->reader.attributes();
const auto &attrs = xr(reader)->reader.attributes();
if (index < 0 || index >= static_cast<int>(attrs.size()))
return OAKCOMMON_E_NOT_FOUND;
return copy_string(attrs[index].value, buf, buf_size);
@@ -178,117 +197,119 @@ int oakcommon_xml_reader_attribute_value(OakCommonXmlReader *reader,
}
}
int oakcommon_xml_reader_has_error(OakCommonXmlReader *reader,
int oakcommon_xml_reader_has_error(OakXmlReader reader,
int *has_error)
{
if (!reader || !has_error)
if (!xr(reader) || !has_error)
return OAKCOMMON_E_INVALID;
try {
*has_error = reader->reader.has_error() ? 1 : 0;
*has_error = xr(reader)->reader.has_error() ? 1 : 0;
return OAKCOMMON_OK;
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
OakCommonXmlWriter *oakcommon_xml_writer_init(void)
OakXmlWriter oakcommon_xml_writer_init(void)
{
try {
return new (std::nothrow) OakCommonXmlWriter();
return oakcommon::make_handle<OakXmlWriter>(
olive::XmlStreamWriter());
} catch (...) {
return nullptr;
OakXmlWriter h = {};
return h;
}
}
void oakcommon_xml_writer_free(OakCommonXmlWriter *writer)
void oakcommon_xml_writer_free(OakXmlWriter *writer)
{
delete writer;
oakcommon::free_handle(writer);
}
int oakcommon_xml_writer_write_start_element(OakCommonXmlWriter *writer,
int oakcommon_xml_writer_write_start_element(OakXmlWriter writer,
const char *name)
{
if (!writer || !name)
if (!xw(writer) || !name)
return OAKCOMMON_E_INVALID;
try {
writer->writer.write_start_element(name);
xw(writer)->write_start_element(name);
return OAKCOMMON_OK;
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
int oakcommon_xml_writer_write_attribute(OakCommonXmlWriter *writer,
int oakcommon_xml_writer_write_attribute(OakXmlWriter writer,
const char *name, const char *value)
{
if (!writer || !name || !value)
if (!xw(writer) || !name || !value)
return OAKCOMMON_E_INVALID;
try {
writer->writer.write_attribute(name, value);
xw(writer)->write_attribute(name, value);
return OAKCOMMON_OK;
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
int oakcommon_xml_writer_write_characters(OakCommonXmlWriter *writer,
int oakcommon_xml_writer_write_characters(OakXmlWriter writer,
const char *text)
{
if (!writer || !text)
if (!xw(writer) || !text)
return OAKCOMMON_E_INVALID;
try {
writer->writer.write_characters(text);
xw(writer)->write_characters(text);
return OAKCOMMON_OK;
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
int oakcommon_xml_writer_write_text_element(OakCommonXmlWriter *writer,
int oakcommon_xml_writer_write_text_element(OakXmlWriter writer,
const char *name,
const char *text)
{
if (!writer || !name || !text)
if (!xw(writer) || !name || !text)
return OAKCOMMON_E_INVALID;
try {
writer->writer.write_text_element(name, text);
xw(writer)->write_text_element(name, text);
return OAKCOMMON_OK;
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
int oakcommon_xml_writer_write_end_element(OakCommonXmlWriter *writer)
int oakcommon_xml_writer_write_end_element(OakXmlWriter writer)
{
if (!writer)
if (!xw(writer))
return OAKCOMMON_E_INVALID;
try {
writer->writer.write_end_element();
xw(writer)->write_end_element();
return OAKCOMMON_OK;
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
int oakcommon_xml_writer_write_end_document(OakCommonXmlWriter *writer)
int oakcommon_xml_writer_write_end_document(OakXmlWriter writer)
{
if (!writer)
if (!xw(writer))
return OAKCOMMON_E_INVALID;
try {
writer->writer.write_end_document();
xw(writer)->write_end_document();
return OAKCOMMON_OK;
} catch (...) {
return OAKCOMMON_E_FAILED;
}
}
int oakcommon_xml_writer_output(OakCommonXmlWriter *writer, char *buf,
int oakcommon_xml_writer_output(OakXmlWriter writer, char *buf,
int buf_size)
{
if (!writer)
if (!xw(writer))
return OAKCOMMON_E_INVALID;
try {
return copy_string(writer->writer.output(), buf, buf_size);
return copy_string(xw(writer)->output(), buf, buf_size);
} catch (...) {
return OAKCOMMON_E_FAILED;
}
+98 -5
View File
@@ -20,12 +20,39 @@
#include "debug.h"
#include <atomic>
#include <cstdio>
#include <cstring>
#include <mutex>
namespace olive
{
namespace
{
/**
* @brief Minimum level emitted by log_message(); default k_debug_info.
*/
std::atomic<int> g_log_level{ k_debug_info };
/**
* @brief Guards g_log_sink (std::function is not atomic).
*/
std::mutex g_sink_mutex;
LogSink g_log_sink;
/**
* @brief Default sink: stderr + flush, same as debug_handler().
*/
void stderr_sink(const std::string &line)
{
fputs(line.c_str(), stderr);
fflush(stderr);
}
} // namespace
int debug_level_name(int level, char *buf, int buf_size)
{
const char *name;
@@ -58,16 +85,82 @@ int debug_level_name(int level, char *buf, int buf_size)
return needed;
}
void debug_handler(int level, const char *msg)
std::string format_log_line(int level, const std::string &msg)
{
char level_name[16];
debug_level_name(level, level_name, sizeof(level_name));
fprintf(stderr, "[%s] %s\n", level_name, msg ? msg : "");
// Always flush so debug messages appear immediately, even on
// platforms that buffer stderr.
fflush(stderr);
std::string line;
line.reserve(strlen(level_name) + msg.size() + 4);
line += '[';
line += level_name;
line += "] ";
line += msg;
line += '\n';
return line;
}
void debug_handler(int level, const char *msg)
{
stderr_sink(format_log_line(level, msg ? msg : ""));
}
void set_log_sink(LogSink sink)
{
std::lock_guard<std::mutex> lock(g_sink_mutex);
g_log_sink = std::move(sink);
}
void set_log_level(DebugLevel level)
{
if (level < k_debug_debug || level > k_debug_fatal)
return;
g_log_level.store(static_cast<int>(level), std::memory_order_relaxed);
}
DebugLevel get_log_level()
{
return static_cast<DebugLevel>(g_log_level.load(std::memory_order_relaxed));
}
void log_message(int level, const std::string &msg)
{
if (level < g_log_level.load(std::memory_order_relaxed))
return;
std::string line = format_log_line(level, msg);
LogSink sink;
{
std::lock_guard<std::mutex> lock(g_sink_mutex);
sink = g_log_sink;
}
// Invoke outside the lock so a sink may log re-entrantly.
if (sink)
sink(line);
else
stderr_sink(line);
}
void log_debug(const std::string &msg)
{
log_message(k_debug_debug, msg);
}
void log_info(const std::string &msg)
{
log_message(k_debug_info, msg);
}
void log_warning(const std::string &msg)
{
log_message(k_debug_warning, msg);
}
void log_critical(const std::string &msg)
{
log_message(k_debug_error, msg);
}
}
+61 -1
View File
@@ -21,6 +21,9 @@
#ifndef OAK_DEBUG_H
#define OAK_DEBUG_H
#include <functional>
#include <string>
namespace olive
{
@@ -28,7 +31,8 @@ namespace olive
* @brief Severity levels for debug output.
*
* Replaces Qt's QtMsgType now that the debug handler no longer depends
* on QDebug.
* on QDebug. Values are ordered by ascending severity so that a simple
* `level < threshold` comparison implements level filtering.
*/
enum DebugLevel {
k_debug_debug,
@@ -56,9 +60,65 @@ int debug_level_name(int level, char *buf, int buf_size);
* De-Qt replacement for the old Qt message handler: qDebug() output is
* replaced by fprintf(stderr). A NULL message is treated as an empty
* string. Lines are always flushed so messages appear immediately.
*
* This is the unfiltered low-level writer; use log_message() (or the
* log_debug()/log_info()/... helpers) for level-filtered logging.
*/
void debug_handler(int level, const char *msg);
/**
* @brief Format a log line as "[LEVEL] message\n".
*/
std::string format_log_line(int level, const std::string &msg);
/**
* @brief Destination for filtered log lines.
*
* Receives the fully formatted line ("[LEVEL] message\n"). The default
* sink writes to stderr and flushes, matching debug_handler().
*/
using LogSink = std::function<void(const std::string &line)>;
/**
* @brief Install a custom log sink (e.g. for tests or log files).
*
* Passing an empty LogSink restores the default stderr sink.
* Thread-safe; the sink is invoked without the internal lock held.
*/
void set_log_sink(LogSink sink);
/**
* @brief Set the minimum level emitted by log_message().
*
* Messages with a lower level are dropped. The default is
* k_debug_info. Values outside the DebugLevel range are ignored.
* Thread-safe (atomic store).
*/
void set_log_level(DebugLevel level);
/**
* @brief The current minimum level emitted by log_message().
*/
DebugLevel get_log_level();
/**
* @brief Emit a message if `level` passes the current level filter.
*
* The formatted line ("[LEVEL] message\n") is handed to the installed
* sink. Messages below the level set with set_log_level() are dropped.
*/
void log_message(int level, const std::string &msg);
/**
* @brief Level-filtered convenience wrappers, replacing qDebug(),
* qInfo(), qWarning() and qCritical(). Callers compose the message
* themselves (plain std::string concatenation).
*/
void log_debug(const std::string &msg);
void log_info(const std::string &msg);
void log_warning(const std::string &msg);
void log_critical(const std::string &msg);
}
#endif // OAK_DEBUG_H
-14
View File
@@ -88,17 +88,3 @@ OIIOUtils::get_pixel_aspect_ratio_from_oiio(const OIIO::ImageSpec &spec)
return olive::core::Rational::from_double(
spec.get_float_attribute("PixelAspectRatio", 1));
}
void OIIOUtils::frame_to_buffer(const void *data, int64_t linesize_bytes,
OIIO::ImageBuf *buf)
{
buf->set_pixels(OIIO::ROI(), buf->spec().format, data, OIIO::AutoStride,
static_cast<OIIO::stride_t>(linesize_bytes));
}
void OIIOUtils::buffer_to_frame(OIIO::ImageBuf *buf, void *data,
int64_t linesize_bytes)
{
buf->get_pixels(OIIO::ROI(), buf->spec().format, data, OIIO::AutoStride,
static_cast<OIIO::stride_t>(linesize_bytes));
}
+3 -20
View File
@@ -35,8 +35,9 @@
* Qt-free reimplementation of the former olive::OIIOUtils. The reverse
* dependencies on codec/frame.h and render/videoparams.h (which pull in
* Qt) were replaced with the Qt-free olive/core/render/pixelformat.h and
* olive/core/util/rational.h. The Frame-based helpers were flattened to
* raw data pointer + linesize, which is all they ever used from Frame.
* olive/core/util/rational.h. The Frame-based helpers (frame_to_buffer /
* buffer_to_frame) moved to oakcodec in M5
* (src/codec/src/oiioframebridge.h) — they are only used by codec.
*/
class OIIOUtils {
public:
@@ -65,24 +66,6 @@ public:
*/
static olive::core::Rational
get_pixel_aspect_ratio_from_oiio(const OIIO::ImageSpec &spec);
/**
* @brief Copies raw pixel data into an OIIO image buffer
*
* Flattened form of the former Frame-based frame_to_buffer(); pass
* Frame::const_data() and Frame::linesize_bytes() at the call site.
*/
static void frame_to_buffer(const void *data, int64_t linesize_bytes,
OIIO::ImageBuf *buf);
/**
* @brief Copies an OIIO image buffer's pixels into raw memory
*
* Flattened form of the former Frame-based buffer_to_frame(); pass
* Frame::data() and Frame::linesize_bytes() at the call site.
*/
static void buffer_to_frame(OIIO::ImageBuf *buf, void *data,
int64_t linesize_bytes);
};
#endif // OAK_OIIOUTILS_H
+2
View File
@@ -26,6 +26,8 @@ add_executable(oakcommon-gtest
dropworkflowbehavior_test.cpp
ffmpegutils_test.cpp
filefunctions_test.cpp
handle_test.cpp
log_test.cpp
memorypool_test.cpp
miscutils_test.cpp
ocioutils_test.cpp
+21 -23
View File
@@ -29,8 +29,8 @@
namespace
{
std::string read_string(int (*fn)(OakCommonColorTransform *, char *, int),
OakCommonColorTransform *t)
std::string read_string(int (*fn)(OakColorTransform, char *, int),
OakColorTransform t)
{
int needed = fn(t, nullptr, 0);
EXPECT_GT(needed, 0);
@@ -43,9 +43,8 @@ std::string read_string(int (*fn)(OakCommonColorTransform *, char *, int),
TEST(CommonColorTransformCApi, InitOutput)
{
OakCommonColorTransform *t =
oakcommon_colortransform_init_output("sRGB");
ASSERT_NE(t, nullptr);
OakColorTransform t = oakcommon_colortransform_init_output("sRGB");
ASSERT_NE(t.ctx, nullptr);
int is_display = -1;
EXPECT_EQ(oakcommon_colortransform_is_display(t, &is_display),
@@ -54,19 +53,19 @@ TEST(CommonColorTransformCApi, InitOutput)
EXPECT_EQ(read_string(oakcommon_colortransform_get_output, t), "sRGB");
EXPECT_EQ(read_string(oakcommon_colortransform_get_display, t), "sRGB");
oakcommon_colortransform_free(t);
oakcommon_colortransform_free(&t);
}
TEST(CommonColorTransformCApi, InitOutputNullString)
{
EXPECT_EQ(oakcommon_colortransform_init_output(nullptr), nullptr);
EXPECT_EQ(oakcommon_colortransform_init_output(nullptr).ctx,
nullptr);
}
TEST(CommonColorTransformCApi, InitDisplay)
{
OakCommonColorTransform *t =
oakcommon_colortransform_init_display("sRGB", "Studio", "None");
ASSERT_NE(t, nullptr);
OakColorTransform t = oakcommon_colortransform_init_display("sRGB", "Studio", "None");
ASSERT_NE(t.ctx, nullptr);
int is_display = 0;
EXPECT_EQ(oakcommon_colortransform_is_display(t, &is_display),
@@ -76,16 +75,16 @@ TEST(CommonColorTransformCApi, InitDisplay)
EXPECT_EQ(read_string(oakcommon_colortransform_get_view, t), "Studio");
EXPECT_EQ(read_string(oakcommon_colortransform_get_look, t), "None");
oakcommon_colortransform_free(t);
oakcommon_colortransform_free(&t);
}
TEST(CommonColorTransformCApi, InitDisplayNullString)
{
EXPECT_EQ(oakcommon_colortransform_init_display(nullptr, "v", "l"),
EXPECT_EQ(oakcommon_colortransform_init_display(nullptr, "v", "l").ctx,
nullptr);
EXPECT_EQ(oakcommon_colortransform_init_display("d", nullptr, "l"),
EXPECT_EQ(oakcommon_colortransform_init_display("d", nullptr, "l").ctx,
nullptr);
EXPECT_EQ(oakcommon_colortransform_init_display("d", "v", nullptr),
EXPECT_EQ(oakcommon_colortransform_init_display("d", "v", nullptr).ctx,
nullptr);
}
@@ -98,24 +97,23 @@ TEST(CommonColorTransformCApi, NullHandleErrors)
{
int i = 0;
char buf[16];
EXPECT_EQ(oakcommon_colortransform_is_display(nullptr, &i),
EXPECT_EQ(oakcommon_colortransform_is_display(OakColorTransform{}, &i),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_colortransform_get_display(nullptr, buf, sizeof(buf)),
EXPECT_EQ(oakcommon_colortransform_get_display(OakColorTransform{}, buf, sizeof(buf)),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_colortransform_get_output(nullptr, buf, sizeof(buf)),
EXPECT_EQ(oakcommon_colortransform_get_output(OakColorTransform{}, buf, sizeof(buf)),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_colortransform_get_view(nullptr, buf, sizeof(buf)),
EXPECT_EQ(oakcommon_colortransform_get_view(OakColorTransform{}, buf, sizeof(buf)),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_colortransform_get_look(nullptr, buf, sizeof(buf)),
EXPECT_EQ(oakcommon_colortransform_get_look(OakColorTransform{}, buf, sizeof(buf)),
OAKCOMMON_E_INVALID);
}
TEST(CommonColorTransformCApi, NullOutParam)
{
OakCommonColorTransform *t =
oakcommon_colortransform_init_output("sRGB");
ASSERT_NE(t, nullptr);
OakColorTransform t = oakcommon_colortransform_init_output("sRGB");
ASSERT_NE(t.ctx, nullptr);
EXPECT_EQ(oakcommon_colortransform_is_display(t, nullptr),
OAKCOMMON_E_INVALID);
oakcommon_colortransform_free(t);
oakcommon_colortransform_free(&t);
}
+52 -53
View File
@@ -27,9 +27,9 @@
TEST(CommandLineParser, InitFree)
{
OakCommonCommandLineParser *parser = oakcommon_commandlineparser_init();
ASSERT_NE(parser, nullptr);
oakcommon_commandlineparser_free(parser);
OakCommandLineParser parser = oakcommon_commandlineparser_init();
ASSERT_NE(parser.ctx, nullptr);
oakcommon_commandlineparser_free(&parser);
}
TEST(CommandLineParser, FreeNull)
@@ -41,14 +41,13 @@ TEST(CommandLineParser, FreeNull)
TEST(CommandLineParser, AddOptionInvalidArgs)
{
OakCommonCommandLineParser *parser = oakcommon_commandlineparser_init();
ASSERT_NE(parser, nullptr);
OakCommandLineParser parser = oakcommon_commandlineparser_init();
ASSERT_NE(parser.ctx, nullptr);
const char *names[] = { "h", "-help" };
OakCommonCommandLineOption *option = nullptr;
OakCommandLineOption option = {};
EXPECT_EQ(oakcommon_commandlineparser_add_option(
nullptr, names, 2, "desc", 0, nullptr, 0, &option),
EXPECT_EQ(oakcommon_commandlineparser_add_option(OakCommandLineParser{}, names, 2, "desc", 0, nullptr, 0, &option),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_commandlineparser_add_option(
parser, nullptr, 2, "desc", 0, nullptr, 0, &option),
@@ -57,28 +56,28 @@ TEST(CommandLineParser, AddOptionInvalidArgs)
parser, names, 0, "desc", 0, nullptr, 0, &option),
OAKCOMMON_E_INVALID);
oakcommon_commandlineparser_free(parser);
oakcommon_commandlineparser_free(&parser);
}
TEST(CommandLineParser, ProcessOptionHitAndMiss)
{
OakCommonCommandLineParser *parser = oakcommon_commandlineparser_init();
ASSERT_NE(parser, nullptr);
OakCommandLineParser parser = oakcommon_commandlineparser_init();
ASSERT_NE(parser.ctx, nullptr);
const char *names[] = { "h", "-help" };
OakCommonCommandLineOption *hit = nullptr;
OakCommonCommandLineOption *miss = nullptr;
OakCommandLineOption hit = {};
OakCommandLineOption miss = {};
ASSERT_EQ(oakcommon_commandlineparser_add_option(
parser, names, 2, "Show help", 0, nullptr, 0, &hit),
OAKCOMMON_OK);
ASSERT_NE(hit, nullptr);
ASSERT_NE(hit.ctx, nullptr);
const char *other_names[] = { "v", "-version" };
ASSERT_EQ(oakcommon_commandlineparser_add_option(
parser, other_names, 2, "Show version", 0, nullptr, 0, &miss),
OAKCOMMON_OK);
ASSERT_NE(miss, nullptr);
ASSERT_NE(miss.ctx, nullptr);
const char *argv[] = { "oak", "-h" };
ASSERT_EQ(oakcommon_commandlineparser_process(parser, argv, 2),
@@ -92,18 +91,18 @@ TEST(CommandLineParser, ProcessOptionHitAndMiss)
ASSERT_EQ(oakcommon_commandlineoption_is_set(miss, &is_set), OAKCOMMON_OK);
EXPECT_FALSE(is_set);
oakcommon_commandlineoption_free(hit);
oakcommon_commandlineoption_free(miss);
oakcommon_commandlineparser_free(parser);
oakcommon_commandlineoption_free(&hit);
oakcommon_commandlineoption_free(&miss);
oakcommon_commandlineparser_free(&parser);
}
TEST(CommandLineParser, ProcessMatchesSecondAliasCaseInsensitive)
{
OakCommonCommandLineParser *parser = oakcommon_commandlineparser_init();
ASSERT_NE(parser, nullptr);
OakCommandLineParser parser = oakcommon_commandlineparser_init();
ASSERT_NE(parser.ctx, nullptr);
const char *names[] = { "h", "-help" };
OakCommonCommandLineOption *option = nullptr;
OakCommandLineOption option = {};
ASSERT_EQ(oakcommon_commandlineparser_add_option(
parser, names, 2, "Show help", 0, nullptr, 0, &option),
OAKCOMMON_OK);
@@ -118,17 +117,17 @@ TEST(CommandLineParser, ProcessMatchesSecondAliasCaseInsensitive)
OAKCOMMON_OK);
EXPECT_TRUE(is_set);
oakcommon_commandlineoption_free(option);
oakcommon_commandlineparser_free(parser);
oakcommon_commandlineoption_free(&option);
oakcommon_commandlineparser_free(&parser);
}
TEST(CommandLineParser, OptionTakesArg)
{
OakCommonCommandLineParser *parser = oakcommon_commandlineparser_init();
ASSERT_NE(parser, nullptr);
OakCommandLineParser parser = oakcommon_commandlineparser_init();
ASSERT_NE(parser.ctx, nullptr);
const char *names[] = { "e", "-export" };
OakCommonCommandLineOption *option = nullptr;
OakCommandLineOption option = {};
ASSERT_EQ(oakcommon_commandlineparser_add_option(
parser, names, 2, "Export", 1, "filename", 0, &option),
OAKCOMMON_OK);
@@ -153,20 +152,20 @@ TEST(CommandLineParser, OptionTakesArg)
required);
EXPECT_STREQ(buf.data(), "/tmp/out.mp4");
oakcommon_commandlineoption_free(option);
oakcommon_commandlineparser_free(parser);
oakcommon_commandlineoption_free(&option);
oakcommon_commandlineparser_free(&parser);
}
TEST(CommandLineParser, PositionalArgument)
{
OakCommonCommandLineParser *parser = oakcommon_commandlineparser_init();
ASSERT_NE(parser, nullptr);
OakCommandLineParser parser = oakcommon_commandlineparser_init();
ASSERT_NE(parser.ctx, nullptr);
OakCommonCommandLinePositionalArgument *arg = nullptr;
OakCommandLinePositionalArgument arg = {};
ASSERT_EQ(oakcommon_commandlineparser_add_positional_argument(
parser, "project", "Project file", 0, &arg),
OAKCOMMON_OK);
ASSERT_NE(arg, nullptr);
ASSERT_NE(arg.ctx, nullptr);
const char *argv[] = { "oak", "/tmp/project.ove" };
ASSERT_EQ(oakcommon_commandlineparser_process(parser, argv, 2),
@@ -182,20 +181,20 @@ TEST(CommandLineParser, PositionalArgument)
required);
EXPECT_STREQ(buf.data(), "/tmp/project.ove");
oakcommon_commandlinepositionalargument_free(arg);
oakcommon_commandlineparser_free(parser);
oakcommon_commandlinepositionalargument_free(&arg);
oakcommon_commandlineparser_free(&parser);
}
TEST(CommandLineParser, PositionalArgumentSetGetSetting)
{
OakCommonCommandLineParser *parser = oakcommon_commandlineparser_init();
ASSERT_NE(parser, nullptr);
OakCommandLineParser parser = oakcommon_commandlineparser_init();
ASSERT_NE(parser.ctx, nullptr);
OakCommonCommandLinePositionalArgument *arg = nullptr;
OakCommandLinePositionalArgument arg = {};
ASSERT_EQ(oakcommon_commandlineparser_add_positional_argument(
parser, "project", "Project file", 0, &arg),
OAKCOMMON_OK);
ASSERT_NE(arg, nullptr);
ASSERT_NE(arg.ctx, nullptr);
ASSERT_EQ(oakcommon_commandlinepositionalargument_set_setting(arg,
"hello.ove"),
@@ -209,28 +208,28 @@ TEST(CommandLineParser, PositionalArgumentSetGetSetting)
EXPECT_STREQ(buf, "hello.ove");
// Error paths
EXPECT_EQ(oakcommon_commandlinepositionalargument_set_setting(nullptr,
EXPECT_EQ(oakcommon_commandlinepositionalargument_set_setting(OakCommandLinePositionalArgument{},
"x"),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_commandlinepositionalargument_set_setting(arg,
nullptr),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_commandlinepositionalargument_get_setting(nullptr,
EXPECT_EQ(oakcommon_commandlinepositionalargument_get_setting(OakCommandLinePositionalArgument{},
buf,
sizeof(buf)),
OAKCOMMON_E_INVALID);
oakcommon_commandlinepositionalargument_free(arg);
oakcommon_commandlineparser_free(parser);
oakcommon_commandlinepositionalargument_free(&arg);
oakcommon_commandlineparser_free(&parser);
}
TEST(CommandLineParser, TwoStageStringGetterSmallBuffer)
{
OakCommonCommandLineParser *parser = oakcommon_commandlineparser_init();
ASSERT_NE(parser, nullptr);
OakCommandLineParser parser = oakcommon_commandlineparser_init();
ASSERT_NE(parser.ctx, nullptr);
const char *names[] = { "e" };
OakCommonCommandLineOption *option = nullptr;
OakCommandLineOption option = {};
ASSERT_EQ(oakcommon_commandlineparser_add_option(
parser, names, 1, "Export", 1, "filename", 0, &option),
OAKCOMMON_OK);
@@ -249,27 +248,27 @@ TEST(CommandLineParser, TwoStageStringGetterSmallBuffer)
EXPECT_EQ(oakcommon_commandlineoption_get_setting(option, nullptr, 0), 7);
// Error paths
EXPECT_EQ(oakcommon_commandlineoption_get_setting(nullptr, buf,
EXPECT_EQ(oakcommon_commandlineoption_get_setting(OakCommandLineOption{}, buf,
sizeof(buf)),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_commandlineoption_is_set(option, nullptr),
OAKCOMMON_E_INVALID);
bool dummy = false;
EXPECT_EQ(oakcommon_commandlineoption_is_set(nullptr, &dummy),
EXPECT_EQ(oakcommon_commandlineoption_is_set(OakCommandLineOption{}, &dummy),
OAKCOMMON_E_INVALID);
oakcommon_commandlineoption_free(option);
oakcommon_commandlineparser_free(parser);
oakcommon_commandlineoption_free(&option);
oakcommon_commandlineparser_free(&parser);
}
TEST(CommandLineParser, ProcessInvalidArgs)
{
EXPECT_EQ(oakcommon_commandlineparser_process(nullptr, nullptr, 0),
EXPECT_EQ(oakcommon_commandlineparser_process(OakCommandLineParser{}, nullptr, 0),
OAKCOMMON_E_INVALID);
OakCommonCommandLineParser *parser = oakcommon_commandlineparser_init();
ASSERT_NE(parser, nullptr);
OakCommandLineParser parser = oakcommon_commandlineparser_init();
ASSERT_NE(parser.ctx, nullptr);
EXPECT_EQ(oakcommon_commandlineparser_process(parser, nullptr, 1),
OAKCOMMON_E_INVALID);
oakcommon_commandlineparser_free(parser);
oakcommon_commandlineparser_free(&parser);
}
+27 -25
View File
@@ -24,25 +24,29 @@
#include "common/current.h"
TEST(OakCommonCurrent, InstanceIsSingleton)
TEST(OakCurrent, InstanceIsSingleton)
{
OakCommonCurrent *a = oakcommon_current_instance();
OakCommonCurrent *b = oakcommon_current_instance();
OakCurrent a = oakcommon_current_instance();
OakCurrent b = oakcommon_current_instance();
ASSERT_NE(a, nullptr);
EXPECT_EQ(a, b);
ASSERT_NE(a.ctx, nullptr);
EXPECT_EQ(a.ctx, b.ctx);
EXPECT_EQ(a.abi_version, OAKCOMMON_ABI_VERSION);
}
TEST(OakCommonCurrent, FreeNullIsNoOp)
TEST(OakCurrent, FreeNullIsNoOp)
{
oakcommon_current_free(nullptr);
oakcommon_current_free(oakcommon_current_instance());
OakCurrent c = oakcommon_current_instance();
oakcommon_current_free(&c);
// Releasing the singleton handle never destroys the object.
EXPECT_NE(c.ctx, nullptr);
SUCCEED();
}
TEST(OakCommonCurrent, VideoParamsSetGetRoundTrip)
TEST(OakCurrent, VideoParamsSetGetRoundTrip)
{
OakCommonCurrent *c = oakcommon_current_instance();
OakCurrent c = oakcommon_current_instance();
int *params = static_cast<int *>(malloc(sizeof(int)));
ASSERT_NE(params, nullptr);
*params = 42;
@@ -62,9 +66,9 @@ TEST(OakCommonCurrent, VideoParamsSetGetRoundTrip)
EXPECT_EQ(out, nullptr);
}
TEST(OakCommonCurrent, AudioParamsSetGetRoundTrip)
TEST(OakCurrent, AudioParamsSetGetRoundTrip)
{
OakCommonCurrent *c = oakcommon_current_instance();
OakCurrent c = oakcommon_current_instance();
int value = 7; // non-owning storage, no destroy callback
ASSERT_EQ(oakcommon_current_set_audio_params(c, &value, nullptr),
@@ -78,9 +82,9 @@ TEST(OakCommonCurrent, AudioParamsSetGetRoundTrip)
OAKCOMMON_OK);
}
TEST(OakCommonCurrent, PluginHostAndCacheRoundTrip)
TEST(OakCurrent, PluginHostAndCacheRoundTrip)
{
OakCommonCurrent *c = oakcommon_current_instance();
OakCurrent c = oakcommon_current_instance();
int host = 1, cache = 2;
ASSERT_EQ(oakcommon_current_set_plugin_host(c, &host, nullptr),
@@ -100,31 +104,29 @@ TEST(OakCommonCurrent, PluginHostAndCacheRoundTrip)
OAKCOMMON_OK);
}
TEST(OakCommonCurrent, NullHandleAndOutArgs)
TEST(OakCurrent, NullHandleAndOutArgs)
{
void *out = nullptr;
int flag = 0;
EXPECT_EQ(oakcommon_current_set_video_params(nullptr, &flag, nullptr),
EXPECT_EQ(oakcommon_current_set_video_params(OakCurrent{}, &flag, nullptr),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_current_get_video_params(nullptr, &out),
EXPECT_EQ(oakcommon_current_get_video_params(OakCurrent{}, &out),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_current_get_video_params(
oakcommon_current_instance(), nullptr),
OakCurrent c = oakcommon_current_instance();
EXPECT_EQ(oakcommon_current_get_video_params(c, nullptr),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_current_is_interactive(nullptr, &flag),
EXPECT_EQ(oakcommon_current_is_interactive(OakCurrent{}, &flag),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_current_is_interactive(
oakcommon_current_instance(), nullptr),
EXPECT_EQ(oakcommon_current_is_interactive(c, nullptr),
OAKCOMMON_E_INVALID);
}
TEST(OakCommonCurrent, IsInteractive)
TEST(OakCurrent, IsInteractive)
{
int flag = 0;
ASSERT_EQ(oakcommon_current_is_interactive(
oakcommon_current_instance(), &flag),
OAKCOMMON_OK);
OakCurrent c = oakcommon_current_instance();
ASSERT_EQ(oakcommon_current_is_interactive(c, &flag), OAKCOMMON_OK);
EXPECT_EQ(flag, 1);
}
+6 -6
View File
@@ -22,25 +22,25 @@
#include "common/debug.h"
TEST(OakCommonDebug, LogValidMessage)
TEST(OakDebug, LogValidMessage)
{
EXPECT_EQ(oakcommon_debug_log(OAKCOMMON_DEBUG_WARNING, "hello"),
OAKCOMMON_OK);
}
TEST(OakCommonDebug, LogNullMessage)
TEST(OakDebug, LogNullMessage)
{
EXPECT_EQ(oakcommon_debug_log(OAKCOMMON_DEBUG_WARNING, nullptr),
OAKCOMMON_E_INVALID);
}
TEST(OakCommonDebug, LogOutOfRangeLevel)
TEST(OakDebug, LogOutOfRangeLevel)
{
// Out-of-range levels are tolerated and print as UNKNOWN.
EXPECT_EQ(oakcommon_debug_log(999, "odd level"), OAKCOMMON_OK);
}
TEST(OakCommonDebug, LevelNameRoundTrip)
TEST(OakDebug, LevelNameRoundTrip)
{
char buf[16];
@@ -51,14 +51,14 @@ TEST(OakCommonDebug, LevelNameRoundTrip)
EXPECT_STREQ(buf, "WARNING");
}
TEST(OakCommonDebug, LevelNameQuerySize)
TEST(OakDebug, LevelNameQuerySize)
{
int needed = oakcommon_debug_level_name(OAKCOMMON_DEBUG_DEBUG,
nullptr, 0);
EXPECT_EQ(needed, 6); // "DEBUG" + NUL
}
TEST(OakCommonDebug, LevelNameUnknownLevel)
TEST(OakDebug, LevelNameUnknownLevel)
{
char buf[16];

Some files were not shown because too many files have changed in this diff Show More