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
+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];
@@ -22,7 +22,7 @@
#include "common/dropworkflowbehavior.h"
TEST(OakCommonDropWorkflowBehavior, IsValidAcceptsAllEnumerators)
TEST(OakDropWorkflowBehavior, IsValidAcceptsAllEnumerators)
{
EXPECT_EQ(oakcommon_drop_workflow_behavior_is_valid(OAKCOMMON_DWS_ASK),
1);
@@ -36,13 +36,13 @@ TEST(OakCommonDropWorkflowBehavior, IsValidAcceptsAllEnumerators)
1);
}
TEST(OakCommonDropWorkflowBehavior, IsValidRejectsOutOfRange)
TEST(OakDropWorkflowBehavior, IsValidRejectsOutOfRange)
{
EXPECT_EQ(oakcommon_drop_workflow_behavior_is_valid(-1), 0);
EXPECT_EQ(oakcommon_drop_workflow_behavior_is_valid(4), 0);
}
TEST(OakCommonDropWorkflowBehavior, NameRoundTrip)
TEST(OakDropWorkflowBehavior, NameRoundTrip)
{
char buf[16];
@@ -53,7 +53,7 @@ TEST(OakCommonDropWorkflowBehavior, NameRoundTrip)
EXPECT_STREQ(buf, "AUTO");
}
TEST(OakCommonDropWorkflowBehavior, NameQuerySizeAndTooSmallBuffer)
TEST(OakDropWorkflowBehavior, NameQuerySizeAndTooSmallBuffer)
{
int needed = oakcommon_drop_workflow_behavior_name(OAKCOMMON_DWS_DISABLE,
nullptr, 0);
@@ -65,7 +65,7 @@ TEST(OakCommonDropWorkflowBehavior, NameQuerySizeAndTooSmallBuffer)
needed);
}
TEST(OakCommonDropWorkflowBehavior, NameInvalidValue)
TEST(OakDropWorkflowBehavior, NameInvalidValue)
{
char buf[16];
+12 -12
View File
@@ -26,7 +26,7 @@
#include "../src/ffmpegutils.h"
TEST(OakCommonFFmpegUtils, GetCompatiblePixelFormatMapsCorrectly)
TEST(OakFFmpegUtils, GetCompatiblePixelFormatMapsCorrectly)
{
int out = -2;
@@ -51,14 +51,14 @@ TEST(OakCommonFFmpegUtils, GetCompatiblePixelFormatMapsCorrectly)
EXPECT_EQ(out, olive::core::PixelFormat::invalid);
}
TEST(OakCommonFFmpegUtils, GetCompatiblePixelFormatNullOut)
TEST(OakFFmpegUtils, GetCompatiblePixelFormatNullOut)
{
EXPECT_EQ(oakcommon_ffmpegutils_get_compatible_pixel_format(
olive::core::PixelFormat::u8, nullptr),
OAKCOMMON_E_INVALID);
}
TEST(OakCommonFFmpegUtils, GetFFmpegPixelFormatMapsCorrectly)
TEST(OakFFmpegUtils, GetFFmpegPixelFormatMapsCorrectly)
{
int out = -2;
@@ -92,7 +92,7 @@ TEST(OakCommonFFmpegUtils, GetFFmpegPixelFormatMapsCorrectly)
EXPECT_EQ(out, fb_pix_fmt_none);
}
TEST(OakCommonFFmpegUtils, GetFFmpegPixelFormatNullOut)
TEST(OakFFmpegUtils, GetFFmpegPixelFormatNullOut)
{
EXPECT_EQ(oakcommon_ffmpegutils_get_ffmpeg_pixel_format(
olive::core::PixelFormat::u8,
@@ -100,7 +100,7 @@ TEST(OakCommonFFmpegUtils, GetFFmpegPixelFormatNullOut)
OAKCOMMON_E_INVALID);
}
TEST(OakCommonFFmpegUtils, GetNativeSampleFormatMapsCorrectly)
TEST(OakFFmpegUtils, GetNativeSampleFormatMapsCorrectly)
{
int out = -2;
@@ -125,14 +125,14 @@ TEST(OakCommonFFmpegUtils, GetNativeSampleFormatMapsCorrectly)
EXPECT_EQ(out, olive::core::SampleFormat::invalid);
}
TEST(OakCommonFFmpegUtils, GetNativeSampleFormatNullOut)
TEST(OakFFmpegUtils, GetNativeSampleFormatNullOut)
{
EXPECT_EQ(oakcommon_ffmpegutils_get_native_sample_format(
fb_sample_fmt_u8, nullptr),
OAKCOMMON_E_INVALID);
}
TEST(OakCommonFFmpegUtils, GetFFmpegSampleFormatMapsCorrectly)
TEST(OakFFmpegUtils, GetFFmpegSampleFormatMapsCorrectly)
{
int out = -2;
@@ -157,14 +157,14 @@ TEST(OakCommonFFmpegUtils, GetFFmpegSampleFormatMapsCorrectly)
EXPECT_EQ(out, fb_sample_fmt_none);
}
TEST(OakCommonFFmpegUtils, GetFFmpegSampleFormatNullOut)
TEST(OakFFmpegUtils, GetFFmpegSampleFormatNullOut)
{
EXPECT_EQ(oakcommon_ffmpegutils_get_ffmpeg_sample_format(
olive::core::SampleFormat::u8, nullptr),
OAKCOMMON_E_INVALID);
}
TEST(OakCommonFFmpegUtils, ConvertJpegSpaceToRegularSpaceMapsCorrectly)
TEST(OakFFmpegUtils, ConvertJpegSpaceToRegularSpaceMapsCorrectly)
{
int out = -2;
@@ -184,21 +184,21 @@ TEST(OakCommonFFmpegUtils, ConvertJpegSpaceToRegularSpaceMapsCorrectly)
EXPECT_EQ(out, fb_pix_fmt_rgba);
}
TEST(OakCommonFFmpegUtils, ConvertJpegSpaceToRegularSpaceNullOut)
TEST(OakFFmpegUtils, ConvertJpegSpaceToRegularSpaceNullOut)
{
EXPECT_EQ(oakcommon_ffmpegutils_convert_jpeg_space_to_regular_space(
fb_pix_fmt_rgba, nullptr),
OAKCOMMON_E_INVALID);
}
TEST(OakCommonFFmpegUtils, GetCompatibleBridgePixelFormatNullOut)
TEST(OakFFmpegUtils, GetCompatibleBridgePixelFormatNullOut)
{
EXPECT_EQ(oakcommon_ffmpegutils_get_compatible_bridge_pixel_format(
fb_pix_fmt_rgba, -1, nullptr),
OAKCOMMON_E_INVALID);
}
TEST(OakCommonFFmpegUtils, GetCompatibleBridgePixelFormatMapsCorrectly)
TEST(OakFFmpegUtils, GetCompatibleBridgePixelFormatMapsCorrectly)
{
/* Calls fb_find_best_pix_fmt_of_list() in ffmpeg_bridge, which
* requires a working FFmpeg runtime environment. */
+17 -24
View File
@@ -35,7 +35,7 @@ protected:
void SetUp() override
{
handle_ = oakcommon_filefunctions_init();
ASSERT_NE(handle_, nullptr);
ASSERT_NE(handle_.ctx, nullptr);
temp_dir_ = fs::temp_directory_path() /
fs::path("oakcommon_filefunctions_test_" +
@@ -46,8 +46,8 @@ protected:
void TearDown() override
{
oakcommon_filefunctions_free(handle_);
handle_ = nullptr;
oakcommon_filefunctions_free(&handle_);
handle_.ctx = nullptr;
std::error_code ec;
fs::remove_all(temp_dir_, ec);
@@ -84,7 +84,7 @@ protected:
return std::string(buf.data());
}
OakCommonFileFunctions *handle_ = nullptr;
OakFileFunctions handle_ = {};
fs::path temp_dir_;
};
@@ -98,43 +98,36 @@ TEST_F(FileFunctionsTest, NullHandleReturnsInvalid)
{
char buf[16];
int out = 0;
EXPECT_EQ(oakcommon_filefunctions_get_configuration_location(
nullptr, buf, sizeof(buf)),
EXPECT_EQ(oakcommon_filefunctions_get_configuration_location(OakFileFunctions{}, buf, sizeof(buf)),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_filefunctions_get_application_path(nullptr, buf,
EXPECT_EQ(oakcommon_filefunctions_get_application_path(OakFileFunctions{}, buf,
sizeof(buf)),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_filefunctions_get_temp_file_path(nullptr, buf,
EXPECT_EQ(oakcommon_filefunctions_get_temp_file_path(OakFileFunctions{}, buf,
sizeof(buf)),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_filefunctions_get_auto_recovery_root(nullptr, buf,
EXPECT_EQ(oakcommon_filefunctions_get_auto_recovery_root(OakFileFunctions{}, buf,
sizeof(buf)),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_filefunctions_get_unique_file_identifier(
nullptr, "x", buf, sizeof(buf)),
EXPECT_EQ(oakcommon_filefunctions_get_unique_file_identifier(OakFileFunctions{}, "x", buf, sizeof(buf)),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_filefunctions_ensure_filename_extension(
nullptr, "x", "y", buf, sizeof(buf)),
EXPECT_EQ(oakcommon_filefunctions_ensure_filename_extension(OakFileFunctions{}, "x", "y", buf, sizeof(buf)),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_filefunctions_read_file_as_string(nullptr, "x",
EXPECT_EQ(oakcommon_filefunctions_read_file_as_string(OakFileFunctions{}, "x",
buf, sizeof(buf)),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_filefunctions_get_safe_temporary_filename(
nullptr, "x", buf, sizeof(buf)),
EXPECT_EQ(oakcommon_filefunctions_get_safe_temporary_filename(OakFileFunctions{}, "x", buf, sizeof(buf)),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_filefunctions_get_formatted_executable_for_platform(
nullptr, "x", buf, sizeof(buf)),
EXPECT_EQ(oakcommon_filefunctions_get_formatted_executable_for_platform(OakFileFunctions{}, "x", buf, sizeof(buf)),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_filefunctions_directory_is_valid(nullptr, "x", 1,
EXPECT_EQ(oakcommon_filefunctions_directory_is_valid(OakFileFunctions{}, "x", 1,
&out),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_filefunctions_can_copy_directory_without_overwriting(
nullptr, "a", "b", &out),
EXPECT_EQ(oakcommon_filefunctions_can_copy_directory_without_overwriting(OakFileFunctions{}, "a", "b", &out),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_filefunctions_copy_directory(nullptr, "a", "b", 0),
EXPECT_EQ(oakcommon_filefunctions_copy_directory(OakFileFunctions{}, "a", "b", 0),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_filefunctions_rename_file_allow_overwrite(
nullptr, "a", "b", &out),
EXPECT_EQ(oakcommon_filefunctions_rename_file_allow_overwrite(OakFileFunctions{}, "a", "b", &out),
OAKCOMMON_E_INVALID);
}
+259
View File
@@ -0,0 +1,259 @@
/***
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 "common/colortransform.h"
#include "common/commandlineparser.h"
#include "common/current.h"
#include "common/filefunctions.h"
#include "common/ocioutils.h"
#include "common/oiioutils.h"
#include "common/subtitleparams.h"
#include "common/videoparams.h"
#include "common/xmlutils.h"
// Native C++ headers (exported through the oakcommon target's public
// include dirs) for the init_from_native tests.
#include "colortransform.h"
#include "subtitleparams.h"
#include "videoparams.h"
namespace
{
/**
* @brief Every init-produced handle must carry the current ABI version
* and non-NULL addref/release thunks.
*/
template <typename Handle> void expect_valid_handle(const Handle &h)
{
EXPECT_NE(h.ctx, nullptr);
EXPECT_NE(h.addref, nullptr);
EXPECT_NE(h.release, nullptr);
EXPECT_EQ(h.abi_version, OAKCOMMON_ABI_VERSION);
}
} // namespace
TEST(OakHandle, AbiVersionStampedEverywhere)
{
OakVideoParams vp = oakcommon_videoparams_init();
expect_valid_handle(vp);
oakcommon_videoparams_free(&vp);
OakSubtitleParams sp = oakcommon_subtitleparams_init();
expect_valid_handle(sp);
oakcommon_subtitleparams_free(&sp);
OakColorTransform ct = oakcommon_colortransform_init_output("sRGB");
expect_valid_handle(ct);
oakcommon_colortransform_free(&ct);
OakCommandLineParser parser = oakcommon_commandlineparser_init();
expect_valid_handle(parser);
const char *names[] = { "h" };
OakCommandLineOption option = {};
ASSERT_EQ(oakcommon_commandlineparser_add_option(
parser, names, 1, "help", 0, nullptr, 0, &option),
OAKCOMMON_OK);
expect_valid_handle(option);
oakcommon_commandlineoption_free(&option);
OakCommandLinePositionalArgument arg = {};
ASSERT_EQ(oakcommon_commandlineparser_add_positional_argument(
parser, "file", "desc", 0, &arg),
OAKCOMMON_OK);
expect_valid_handle(arg);
oakcommon_commandlinepositionalargument_free(&arg);
oakcommon_commandlineparser_free(&parser);
OakXmlReader reader =
oakcommon_xml_reader_init("<root/>");
expect_valid_handle(reader);
oakcommon_xml_reader_free(&reader);
OakXmlWriter writer = oakcommon_xml_writer_init();
expect_valid_handle(writer);
oakcommon_xml_writer_free(&writer);
OakFileFunctions ff = oakcommon_filefunctions_init();
expect_valid_handle(ff);
oakcommon_filefunctions_free(&ff);
OakOCIOUtils ocio = oakcommon_ocioutils_init();
expect_valid_handle(ocio);
oakcommon_ocioutils_free(&ocio);
OakOIIOUtils oiio = oakcommon_oiioutils_init();
expect_valid_handle(oiio);
oakcommon_oiioutils_free(&oiio);
OakCurrent current = oakcommon_current_instance();
expect_valid_handle(current);
}
TEST(OakHandle, AddrefReleaseCountSemantics)
{
OakVideoParams h = oakcommon_videoparams_init();
ASSERT_NE(h.ctx, nullptr);
ASSERT_EQ(oakcommon_videoparams_set_width(h, 1920), OAKCOMMON_OK);
// Copy the handle struct and take a second reference through the
// function pointer, like a foreign (Rust/DLL) consumer would.
OakVideoParams copy = h;
h.addref(h.ctx);
// Dropping the first reference must not destroy the object: the copy
// is still fully usable.
h.release(h.ctx);
int width = 0;
ASSERT_EQ(oakcommon_videoparams_get_width(copy, &width), OAKCOMMON_OK);
EXPECT_EQ(width, 1920);
// Dropping the last reference destroys the object; free() clears ctx.
oakcommon_videoparams_free(&copy);
EXPECT_EQ(copy.ctx, nullptr);
}
TEST(OakHandle, FreeNullAndEmptyCtxAreNoOp)
{
// NULL handle pointer.
oakcommon_videoparams_free(nullptr);
oakcommon_subtitleparams_free(nullptr);
oakcommon_colortransform_free(nullptr);
oakcommon_commandlineparser_free(nullptr);
oakcommon_commandlineoption_free(nullptr);
oakcommon_commandlinepositionalargument_free(nullptr);
oakcommon_xml_reader_free(nullptr);
oakcommon_xml_writer_free(nullptr);
oakcommon_filefunctions_free(nullptr);
oakcommon_ocioutils_free(nullptr);
oakcommon_oiioutils_free(nullptr);
oakcommon_current_free(nullptr);
// Handle whose ctx is NULL (e.g. after a failed init or a free).
OakVideoParams h = {};
oakcommon_videoparams_free(&h);
int width = 0;
EXPECT_EQ(oakcommon_videoparams_get_width(h, &width),
OAKCOMMON_E_INVALID);
// release() itself must tolerate a NULL ctx (foreign consumers may
// call it directly).
h.release = nullptr; // no thunk available on a zero-initialized handle
oakcommon_videoparams_free(&h);
SUCCEED();
}
TEST(OakHandle, VideoParamsFromNativeSurvivesSource)
{
OakVideoParams h = {};
{
olive::VideoParams native(
1920, 1080, olive::core::Rational(1, 25),
olive::core::PixelFormat::u8, 4, olive::core::Rational(1, 1),
olive::VideoParams::k_interlace_none, 1);
h = oakcommon_videoparams_init_from_native(&native);
ASSERT_NE(h.ctx, nullptr);
EXPECT_EQ(h.abi_version, OAKCOMMON_ABI_VERSION);
} // native stack object destroyed here
int width = 0, height = 0, num = 0, den = 0;
ASSERT_EQ(oakcommon_videoparams_get_width(h, &width), OAKCOMMON_OK);
ASSERT_EQ(oakcommon_videoparams_get_height(h, &height), OAKCOMMON_OK);
ASSERT_EQ(oakcommon_videoparams_get_time_base(h, &num, &den),
OAKCOMMON_OK);
EXPECT_EQ(width, 1920);
EXPECT_EQ(height, 1080);
EXPECT_EQ(num, 1);
EXPECT_EQ(den, 25);
oakcommon_videoparams_free(&h);
// NULL source yields an empty handle, not a crash.
OakVideoParams empty =
oakcommon_videoparams_init_from_native(nullptr);
EXPECT_EQ(empty.ctx, nullptr);
}
TEST(OakHandle, SubtitleParamsFromNativeSurvivesSource)
{
OakSubtitleParams h = {};
{
olive::SubtitleParams native;
native.push_back(olive::Subtitle(
olive::core::TimeRange(olive::core::Rational(0, 1),
olive::core::Rational(2, 1)),
"hello"));
h = oakcommon_subtitleparams_init_from_native(&native);
ASSERT_NE(h.ctx, nullptr);
} // native stack object destroyed here
int count = 0;
ASSERT_EQ(oakcommon_subtitleparams_count(h, &count), OAKCOMMON_OK);
EXPECT_EQ(count, 1);
char buf[16];
int needed = oakcommon_subtitleparams_get_subtitle_text(h, 0, buf,
sizeof(buf));
ASSERT_EQ(needed, 6);
EXPECT_STREQ(buf, "hello");
oakcommon_subtitleparams_free(&h);
}
TEST(OakHandle, ColorTransformFromNativeSurvivesSource)
{
OakColorTransform h = {};
{
olive::ColorTransform native(std::string("Display"),
std::string("Standard"),
std::string("None"));
h = oakcommon_colortransform_init_from_native(&native);
ASSERT_NE(h.ctx, nullptr);
} // native stack object destroyed here
int is_display = 0;
ASSERT_EQ(oakcommon_colortransform_is_display(h, &is_display),
OAKCOMMON_OK);
EXPECT_EQ(is_display, 1);
char buf[16];
int needed = oakcommon_colortransform_get_view(h, buf, sizeof(buf));
ASSERT_EQ(needed, 9);
EXPECT_STREQ(buf, "Standard");
oakcommon_colortransform_free(&h);
}
TEST(OakHandle, CurrentSingletonReleaseNeverDestroys)
{
OakCurrent h = oakcommon_current_instance();
ASSERT_NE(h.ctx, nullptr);
void *ctx = h.ctx;
// The singleton's addref/release are deliberate no-ops.
h.addref(h.ctx);
h.release(h.ctx);
oakcommon_current_free(&h);
OakCurrent again = oakcommon_current_instance();
EXPECT_EQ(again.ctx, ctx);
}
+174
View File
@@ -0,0 +1,174 @@
/***
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 <string>
#include <vector>
#include <gtest/gtest.h>
#include "common/debug.h"
// Native C++ header (exported through the oakcommon target's public
// include dirs) for sink injection.
#include "debug.h"
namespace
{
/**
* @brief RAII helper: captures all filtered log lines into a vector and
* restores the default stderr sink and level on destruction.
*/
class LogCapture {
public:
LogCapture()
{
olive::set_log_sink([this](const std::string &line) {
lines.push_back(line);
});
}
~LogCapture()
{
olive::set_log_sink(nullptr);
olive::set_log_level(olive::k_debug_info);
}
std::vector<std::string> lines;
};
} // namespace
TEST(OakLog, LevelSetGetRoundTrip)
{
ASSERT_EQ(oakcommon_log_set_level(OAKCOMMON_DEBUG_WARNING),
OAKCOMMON_OK);
int level = -1;
ASSERT_EQ(oakcommon_log_get_level(&level), OAKCOMMON_OK);
EXPECT_EQ(level, OAKCOMMON_DEBUG_WARNING);
ASSERT_EQ(oakcommon_log_set_level(OAKCOMMON_DEBUG_DEBUG),
OAKCOMMON_OK);
ASSERT_EQ(oakcommon_log_get_level(&level), OAKCOMMON_OK);
EXPECT_EQ(level, OAKCOMMON_DEBUG_DEBUG);
// Restore default for other tests.
ASSERT_EQ(oakcommon_log_set_level(OAKCOMMON_DEBUG_INFO), OAKCOMMON_OK);
}
TEST(OakLog, LevelSetGetInvalidArgs)
{
EXPECT_EQ(oakcommon_log_set_level(-1), OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_log_set_level(999), OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_log_get_level(nullptr), OAKCOMMON_E_INVALID);
// The failed sets above must not have changed the level.
int level = -1;
ASSERT_EQ(oakcommon_log_get_level(&level), OAKCOMMON_OK);
EXPECT_EQ(level, OAKCOMMON_DEBUG_INFO);
}
TEST(OakLog, LevelFilterDropsLowerLevels)
{
LogCapture capture;
ASSERT_EQ(oakcommon_log_set_level(OAKCOMMON_DEBUG_WARNING),
OAKCOMMON_OK);
EXPECT_EQ(oakcommon_log(OAKCOMMON_DEBUG_DEBUG, "dbg"), OAKCOMMON_OK);
EXPECT_EQ(oakcommon_log(OAKCOMMON_DEBUG_INFO, "inf"), OAKCOMMON_OK);
EXPECT_TRUE(capture.lines.empty());
EXPECT_EQ(oakcommon_log(OAKCOMMON_DEBUG_WARNING, "wrn"), OAKCOMMON_OK);
EXPECT_EQ(oakcommon_log(OAKCOMMON_DEBUG_ERROR, "err"), OAKCOMMON_OK);
ASSERT_EQ(capture.lines.size(), 2u);
EXPECT_EQ(capture.lines[0], "[WARNING] wrn\n");
EXPECT_EQ(capture.lines[1], "[ERROR] err\n");
}
TEST(OakLog, DefaultLevelIsInfo)
{
// The default filter is INFO: DEBUG is dropped, INFO passes.
LogCapture capture;
olive::log_debug("invisible");
olive::log_info("visible");
ASSERT_EQ(capture.lines.size(), 1u);
EXPECT_EQ(capture.lines[0], "[INFO] visible\n");
}
TEST(OakLog, ConvenienceWrappersUseTheirLevels)
{
LogCapture capture;
olive::set_log_level(olive::k_debug_debug);
olive::log_debug("d");
olive::log_info("i");
olive::log_warning("w");
olive::log_critical("c");
ASSERT_EQ(capture.lines.size(), 4u);
EXPECT_EQ(capture.lines[0], "[DEBUG] d\n");
EXPECT_EQ(capture.lines[1], "[INFO] i\n");
EXPECT_EQ(capture.lines[2], "[WARNING] w\n");
EXPECT_EQ(capture.lines[3], "[ERROR] c\n");
}
TEST(OakLog, PrintfFormatting)
{
LogCapture capture;
olive::set_log_level(olive::k_debug_debug);
EXPECT_EQ(oakcommon_log(OAKCOMMON_DEBUG_INFO, "w=%d h=%d name=%s",
1920, 1080, "clip"),
OAKCOMMON_OK);
ASSERT_EQ(capture.lines.size(), 1u);
EXPECT_EQ(capture.lines[0], "[INFO] w=1920 h=1080 name=clip\n");
}
TEST(OakLog, PrintfLongMessageNotTruncated)
{
LogCapture capture;
std::string long_msg(100 * 1024, 'x');
EXPECT_EQ(oakcommon_log(OAKCOMMON_DEBUG_WARNING, "%s",
long_msg.c_str()),
OAKCOMMON_OK);
ASSERT_EQ(capture.lines.size(), 1u);
EXPECT_EQ(capture.lines[0],
"[WARNING] " + long_msg + "\n");
}
TEST(OakLog, PrintfNullFormat)
{
EXPECT_EQ(oakcommon_log(OAKCOMMON_DEBUG_WARNING, nullptr),
OAKCOMMON_E_INVALID);
}
TEST(OakLog, OutOfRangeLevelPrintsUnknown)
{
LogCapture capture;
olive::set_log_level(olive::k_debug_debug);
// Out-of-range levels are tolerated and print as UNKNOWN (same as
// oakcommon_debug_log).
EXPECT_EQ(oakcommon_log(999, "odd"), OAKCOMMON_OK);
ASSERT_EQ(capture.lines.size(), 1u);
EXPECT_EQ(capture.lines[0], "[UNKNOWN] odd\n");
}
+16 -17
View File
@@ -34,9 +34,9 @@ namespace ocio = OCIO_NAMESPACE;
TEST(OCIOUtilsCApi, InitReturnsHandle)
{
OakCommonOCIOUtils *utils = oakcommon_ocioutils_init();
ASSERT_NE(utils, nullptr);
oakcommon_ocioutils_free(utils);
OakOCIOUtils utils = oakcommon_ocioutils_init();
ASSERT_NE(utils.ctx, nullptr);
oakcommon_ocioutils_free(&utils);
}
TEST(OCIOUtilsCApi, FreeNullIsNoOp)
@@ -46,8 +46,8 @@ TEST(OCIOUtilsCApi, FreeNullIsNoOp)
TEST(OCIOUtilsCApi, BitDepthMappingMatchesOCIO)
{
OakCommonOCIOUtils *utils = oakcommon_ocioutils_init();
ASSERT_NE(utils, nullptr);
OakOCIOUtils utils = oakcommon_ocioutils_init();
ASSERT_NE(utils.ctx, nullptr);
const struct {
int pixel_format;
@@ -68,13 +68,13 @@ TEST(OCIOUtilsCApi, BitDepthMappingMatchesOCIO)
EXPECT_EQ(depth, static_cast<int>(c.expected));
}
oakcommon_ocioutils_free(utils);
oakcommon_ocioutils_free(&utils);
}
TEST(OCIOUtilsCApi, InvalidFormatYieldsUnknownDepth)
{
OakCommonOCIOUtils *utils = oakcommon_ocioutils_init();
ASSERT_NE(utils, nullptr);
OakOCIOUtils utils = oakcommon_ocioutils_init();
ASSERT_NE(utils.ctx, nullptr);
int depth = -1;
EXPECT_EQ(oakcommon_ocioutils_get_ocio_bit_depth_from_pixel_format(
@@ -82,33 +82,32 @@ TEST(OCIOUtilsCApi, InvalidFormatYieldsUnknownDepth)
OAKCOMMON_OK);
EXPECT_EQ(depth, static_cast<int>(ocio::BIT_DEPTH_UNKNOWN));
oakcommon_ocioutils_free(utils);
oakcommon_ocioutils_free(&utils);
}
TEST(OCIOUtilsCApi, NullHandleReturnsInvalid)
{
int depth = 0;
EXPECT_EQ(oakcommon_ocioutils_get_ocio_bit_depth_from_pixel_format(
NULL, OAKCOMMON_PIXEL_FORMAT_U8, &depth),
EXPECT_EQ(oakcommon_ocioutils_get_ocio_bit_depth_from_pixel_format(OakOCIOUtils{}, OAKCOMMON_PIXEL_FORMAT_U8, &depth),
OAKCOMMON_E_INVALID);
}
TEST(OCIOUtilsCApi, NullOutParamReturnsInvalid)
{
OakCommonOCIOUtils *utils = oakcommon_ocioutils_init();
ASSERT_NE(utils, nullptr);
OakOCIOUtils utils = oakcommon_ocioutils_init();
ASSERT_NE(utils.ctx, nullptr);
EXPECT_EQ(oakcommon_ocioutils_get_ocio_bit_depth_from_pixel_format(
utils, OAKCOMMON_PIXEL_FORMAT_U8, NULL),
OAKCOMMON_E_INVALID);
oakcommon_ocioutils_free(utils);
oakcommon_ocioutils_free(&utils);
}
TEST(OCIOUtilsCApi, OutOfRangeFormatReturnsInvalid)
{
OakCommonOCIOUtils *utils = oakcommon_ocioutils_init();
ASSERT_NE(utils, nullptr);
OakOCIOUtils utils = oakcommon_ocioutils_init();
ASSERT_NE(utils.ctx, nullptr);
int depth = 0;
EXPECT_EQ(oakcommon_ocioutils_get_ocio_bit_depth_from_pixel_format(
@@ -118,5 +117,5 @@ TEST(OCIOUtilsCApi, OutOfRangeFormatReturnsInvalid)
utils, -2, &depth),
OAKCOMMON_E_INVALID);
oakcommon_ocioutils_free(utils);
oakcommon_ocioutils_free(&utils);
}
+24 -26
View File
@@ -37,9 +37,9 @@
TEST(OIIOUtilsCApi, InitReturnsHandle)
{
OakCommonOIIOUtils *utils = oakcommon_oiioutils_init();
ASSERT_NE(utils, nullptr);
oakcommon_oiioutils_free(utils);
OakOIIOUtils utils = oakcommon_oiioutils_init();
ASSERT_NE(utils.ctx, nullptr);
oakcommon_oiioutils_free(&utils);
}
TEST(OIIOUtilsCApi, FreeNullIsNoOp)
@@ -49,8 +49,8 @@ TEST(OIIOUtilsCApi, FreeNullIsNoOp)
TEST(OIIOUtilsCApi, BaseTypeFromPixelFormat)
{
OakCommonOIIOUtils *utils = oakcommon_oiioutils_init();
ASSERT_NE(utils, nullptr);
OakOIIOUtils utils = oakcommon_oiioutils_init();
ASSERT_NE(utils.ctx, nullptr);
const struct {
int pixel_format;
@@ -72,17 +72,16 @@ TEST(OIIOUtilsCApi, BaseTypeFromPixelFormat)
EXPECT_EQ(base_type, c.expected_base_type);
}
oakcommon_oiioutils_free(utils);
oakcommon_oiioutils_free(&utils);
}
TEST(OIIOUtilsCApi, BaseTypeFromPixelFormatRejectsBadArgs)
{
OakCommonOIIOUtils *utils = oakcommon_oiioutils_init();
ASSERT_NE(utils, nullptr);
OakOIIOUtils utils = oakcommon_oiioutils_init();
ASSERT_NE(utils.ctx, nullptr);
int base_type = 0;
EXPECT_EQ(oakcommon_oiioutils_get_oiio_base_type_from_format(
NULL, OAKCOMMON_PIXEL_FORMAT_U8, &base_type),
EXPECT_EQ(oakcommon_oiioutils_get_oiio_base_type_from_format(OakOIIOUtils{}, OAKCOMMON_PIXEL_FORMAT_U8, &base_type),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_oiioutils_get_oiio_base_type_from_format(
utils, OAKCOMMON_PIXEL_FORMAT_U8, NULL),
@@ -94,13 +93,13 @@ TEST(OIIOUtilsCApi, BaseTypeFromPixelFormatRejectsBadArgs)
utils, -2, &base_type),
OAKCOMMON_E_INVALID);
oakcommon_oiioutils_free(utils);
oakcommon_oiioutils_free(&utils);
}
TEST(OIIOUtilsCApi, PixelFormatFromBaseType)
{
OakCommonOIIOUtils *utils = oakcommon_oiioutils_init();
ASSERT_NE(utils, nullptr);
OakOIIOUtils utils = oakcommon_oiioutils_init();
ASSERT_NE(utils.ctx, nullptr);
const struct {
int base_type;
@@ -123,17 +122,16 @@ TEST(OIIOUtilsCApi, PixelFormatFromBaseType)
EXPECT_EQ(pixel_format, c.expected_pixel_format);
}
oakcommon_oiioutils_free(utils);
oakcommon_oiioutils_free(&utils);
}
TEST(OIIOUtilsCApi, PixelFormatFromBaseTypeRejectsBadArgs)
{
OakCommonOIIOUtils *utils = oakcommon_oiioutils_init();
ASSERT_NE(utils, nullptr);
OakOIIOUtils utils = oakcommon_oiioutils_init();
ASSERT_NE(utils.ctx, nullptr);
int pixel_format = 0;
EXPECT_EQ(oakcommon_oiioutils_get_format_from_oiio_basetype(
NULL, 2, &pixel_format),
EXPECT_EQ(oakcommon_oiioutils_get_format_from_oiio_basetype(OakOIIOUtils{}, 2, &pixel_format),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_oiioutils_get_format_from_oiio_basetype(
utils, 2, NULL),
@@ -145,13 +143,13 @@ TEST(OIIOUtilsCApi, PixelFormatFromBaseTypeRejectsBadArgs)
utils, 100, &pixel_format),
OAKCOMMON_E_INVALID); /* >= LASTBASE */
oakcommon_oiioutils_free(utils);
oakcommon_oiioutils_free(&utils);
}
TEST(OIIOUtilsCApi, PixelAspectRatioConvertsToRational)
{
OakCommonOIIOUtils *utils = oakcommon_oiioutils_init();
ASSERT_NE(utils, nullptr);
OakOIIOUtils utils = oakcommon_oiioutils_init();
ASSERT_NE(utils.ctx, nullptr);
int num = 0;
int den = 0;
@@ -167,17 +165,17 @@ TEST(OIIOUtilsCApi, PixelAspectRatioConvertsToRational)
ASSERT_NE(den, 0);
EXPECT_NEAR(static_cast<double>(num) / den, 1.5, 1e-9);
oakcommon_oiioutils_free(utils);
oakcommon_oiioutils_free(&utils);
}
TEST(OIIOUtilsCApi, PixelAspectRatioRejectsBadArgs)
{
OakCommonOIIOUtils *utils = oakcommon_oiioutils_init();
ASSERT_NE(utils, nullptr);
OakOIIOUtils utils = oakcommon_oiioutils_init();
ASSERT_NE(utils.ctx, nullptr);
int num = 0;
int den = 0;
EXPECT_EQ(oakcommon_oiioutils_get_pixel_aspect_ratio(NULL, 1.0, &num,
EXPECT_EQ(oakcommon_oiioutils_get_pixel_aspect_ratio(OakOIIOUtils{}, 1.0, &num,
&den),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_oiioutils_get_pixel_aspect_ratio(utils, 1.0, NULL,
@@ -187,5 +185,5 @@ TEST(OIIOUtilsCApi, PixelAspectRatioRejectsBadArgs)
NULL),
OAKCOMMON_E_INVALID);
oakcommon_oiioutils_free(utils);
oakcommon_oiioutils_free(&utils);
}
+39 -39
View File
@@ -30,8 +30,8 @@ namespace
{
std::string read_indexed_string(
int (*fn)(OakCommonSubtitleParams *, int, char *, int),
OakCommonSubtitleParams *p, int index)
int (*fn)(OakSubtitleParams, int, char *, int),
OakSubtitleParams p, int index)
{
int needed = fn(p, index, nullptr, 0);
EXPECT_GT(needed, 0);
@@ -53,8 +53,8 @@ std::string read_static_string(int (*fn)(char *, int))
TEST(CommonSubtitleParamsCApi, InitFree)
{
OakCommonSubtitleParams *p = oakcommon_subtitleparams_init();
ASSERT_NE(p, nullptr);
OakSubtitleParams p = oakcommon_subtitleparams_init();
ASSERT_NE(p.ctx, nullptr);
int index = -1;
EXPECT_EQ(oakcommon_subtitleparams_get_stream_index(p, &index),
@@ -66,7 +66,7 @@ TEST(CommonSubtitleParamsCApi, InitFree)
OAKCOMMON_OK);
EXPECT_EQ(enabled, 1);
oakcommon_subtitleparams_free(p);
oakcommon_subtitleparams_free(&p);
}
TEST(CommonSubtitleParamsCApi, FreeNull)
@@ -76,48 +76,48 @@ TEST(CommonSubtitleParamsCApi, FreeNull)
TEST(CommonSubtitleParamsCApi, SetStreamIndex)
{
OakCommonSubtitleParams *p = oakcommon_subtitleparams_init();
ASSERT_NE(p, nullptr);
OakSubtitleParams p = oakcommon_subtitleparams_init();
ASSERT_NE(p.ctx, nullptr);
EXPECT_EQ(oakcommon_subtitleparams_set_stream_index(p, 3), OAKCOMMON_OK);
int index = 0;
EXPECT_EQ(oakcommon_subtitleparams_get_stream_index(p, &index),
OAKCOMMON_OK);
EXPECT_EQ(index, 3);
oakcommon_subtitleparams_free(p);
oakcommon_subtitleparams_free(&p);
}
TEST(CommonSubtitleParamsCApi, SetStreamIndexNullHandle)
{
EXPECT_EQ(oakcommon_subtitleparams_set_stream_index(nullptr, 3),
EXPECT_EQ(oakcommon_subtitleparams_set_stream_index(OakSubtitleParams{}, 3),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_subtitleparams_get_stream_index(nullptr, nullptr),
EXPECT_EQ(oakcommon_subtitleparams_get_stream_index(OakSubtitleParams{}, nullptr),
OAKCOMMON_E_INVALID);
}
TEST(CommonSubtitleParamsCApi, SetEnabled)
{
OakCommonSubtitleParams *p = oakcommon_subtitleparams_init();
ASSERT_NE(p, nullptr);
OakSubtitleParams p = oakcommon_subtitleparams_init();
ASSERT_NE(p.ctx, nullptr);
EXPECT_EQ(oakcommon_subtitleparams_set_enabled(p, 0), OAKCOMMON_OK);
int enabled = 1;
EXPECT_EQ(oakcommon_subtitleparams_get_enabled(p, &enabled),
OAKCOMMON_OK);
EXPECT_EQ(enabled, 0);
oakcommon_subtitleparams_free(p);
oakcommon_subtitleparams_free(&p);
}
TEST(CommonSubtitleParamsCApi, SetEnabledNullHandle)
{
EXPECT_EQ(oakcommon_subtitleparams_set_enabled(nullptr, 0),
EXPECT_EQ(oakcommon_subtitleparams_set_enabled(OakSubtitleParams{}, 0),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_subtitleparams_get_enabled(nullptr, nullptr),
EXPECT_EQ(oakcommon_subtitleparams_get_enabled(OakSubtitleParams{}, nullptr),
OAKCOMMON_E_INVALID);
}
TEST(CommonSubtitleParamsCApi, AddAndReadSubtitles)
{
OakCommonSubtitleParams *p = oakcommon_subtitleparams_init();
ASSERT_NE(p, nullptr);
OakSubtitleParams p = oakcommon_subtitleparams_init();
ASSERT_NE(p.ctx, nullptr);
int is_valid = 1;
EXPECT_EQ(oakcommon_subtitleparams_is_valid(p, &is_valid), OAKCOMMON_OK);
@@ -156,40 +156,40 @@ TEST(CommonSubtitleParamsCApi, AddAndReadSubtitles)
EXPECT_EQ(oakcommon_subtitleparams_count(p, &count), OAKCOMMON_OK);
EXPECT_EQ(count, 0);
oakcommon_subtitleparams_free(p);
oakcommon_subtitleparams_free(&p);
}
TEST(CommonSubtitleParamsCApi, SubtitleErrorPaths)
{
OakCommonSubtitleParams *p = oakcommon_subtitleparams_init();
ASSERT_NE(p, nullptr);
OakSubtitleParams p = oakcommon_subtitleparams_init();
ASSERT_NE(p.ctx, nullptr);
int i = 0;
char buf[16];
EXPECT_EQ(oakcommon_subtitleparams_add_subtitle(nullptr, 0, 1, 1, 1, "x"),
EXPECT_EQ(oakcommon_subtitleparams_add_subtitle(OakSubtitleParams{}, 0, 1, 1, 1, "x"),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_subtitleparams_add_subtitle(p, 0, 1, 1, 1, nullptr),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_subtitleparams_get_subtitle(p, 0, &i, &i, &i, &i),
OAKCOMMON_E_NOT_FOUND);
EXPECT_EQ(oakcommon_subtitleparams_get_subtitle(nullptr, 0, &i, &i, &i,
EXPECT_EQ(oakcommon_subtitleparams_get_subtitle(OakSubtitleParams{}, 0, &i, &i, &i,
&i),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_subtitleparams_get_subtitle_text(p, 5, buf,
sizeof(buf)),
OAKCOMMON_E_NOT_FOUND);
EXPECT_EQ(oakcommon_subtitleparams_get_subtitle_text(nullptr, 0, buf,
EXPECT_EQ(oakcommon_subtitleparams_get_subtitle_text(OakSubtitleParams{}, 0, buf,
sizeof(buf)),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_subtitleparams_is_valid(nullptr, &i),
EXPECT_EQ(oakcommon_subtitleparams_is_valid(OakSubtitleParams{}, &i),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_subtitleparams_count(nullptr, &i),
EXPECT_EQ(oakcommon_subtitleparams_count(OakSubtitleParams{}, &i),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_subtitleparams_duration(nullptr, &i, &i),
EXPECT_EQ(oakcommon_subtitleparams_duration(OakSubtitleParams{}, &i, &i),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_subtitleparams_clear(nullptr), OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_subtitleparams_clear(OakSubtitleParams{}), OAKCOMMON_E_INVALID);
oakcommon_subtitleparams_free(p);
oakcommon_subtitleparams_free(&p);
}
TEST(CommonSubtitleParamsCApi, GenerateAssHeader)
@@ -203,8 +203,8 @@ TEST(CommonSubtitleParamsCApi, GenerateAssHeader)
TEST(CommonSubtitleParamsCApi, XmlRoundTrip)
{
OakCommonSubtitleParams *p = oakcommon_subtitleparams_init();
ASSERT_NE(p, nullptr);
OakSubtitleParams p = oakcommon_subtitleparams_init();
ASSERT_NE(p.ctx, nullptr);
EXPECT_EQ(oakcommon_subtitleparams_set_stream_index(p, 2), OAKCOMMON_OK);
EXPECT_EQ(oakcommon_subtitleparams_add_subtitle(p, 1, 25, 2, 25,
"First <line>"),
@@ -215,8 +215,8 @@ TEST(CommonSubtitleParamsCApi, XmlRoundTrip)
std::vector<char> buf(needed);
ASSERT_EQ(oakcommon_subtitleparams_save_xml(p, buf.data(), needed), needed);
OakCommonSubtitleParams *q = oakcommon_subtitleparams_init();
ASSERT_NE(q, nullptr);
OakSubtitleParams q = oakcommon_subtitleparams_init();
ASSERT_NE(q.ctx, nullptr);
ASSERT_EQ(oakcommon_subtitleparams_load_xml(q, buf.data()), OAKCOMMON_OK);
int index = 0;
@@ -230,22 +230,22 @@ TEST(CommonSubtitleParamsCApi, XmlRoundTrip)
oakcommon_subtitleparams_get_subtitle_text, q, 0),
"First <line>");
oakcommon_subtitleparams_free(p);
oakcommon_subtitleparams_free(q);
oakcommon_subtitleparams_free(&p);
oakcommon_subtitleparams_free(&q);
}
TEST(CommonSubtitleParamsCApi, XmlErrorPaths)
{
OakCommonSubtitleParams *p = oakcommon_subtitleparams_init();
ASSERT_NE(p, nullptr);
OakSubtitleParams p = oakcommon_subtitleparams_init();
ASSERT_NE(p.ctx, nullptr);
char buf[16];
EXPECT_EQ(oakcommon_subtitleparams_load_xml(nullptr, "<a/>"),
EXPECT_EQ(oakcommon_subtitleparams_load_xml(OakSubtitleParams{}, "<a/>"),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_subtitleparams_load_xml(p, nullptr),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_subtitleparams_load_xml(p, "not xml"),
OAKCOMMON_E_FAILED);
EXPECT_EQ(oakcommon_subtitleparams_save_xml(nullptr, buf, sizeof(buf)),
EXPECT_EQ(oakcommon_subtitleparams_save_xml(OakSubtitleParams{}, buf, sizeof(buf)),
OAKCOMMON_E_INVALID);
oakcommon_subtitleparams_free(p);
oakcommon_subtitleparams_free(&p);
}
+95 -95
View File
@@ -29,8 +29,8 @@
TEST(CommonVideoParamsCApi, InitDefaults)
{
OakCommonVideoParams *p = oakcommon_videoparams_init();
ASSERT_NE(p, nullptr);
OakVideoParams p = oakcommon_videoparams_init();
ASSERT_NE(p.ctx, nullptr);
int v = -1;
EXPECT_EQ(oakcommon_videoparams_get_width(p, &v), OAKCOMMON_OK);
@@ -38,7 +38,7 @@ TEST(CommonVideoParamsCApi, InitDefaults)
EXPECT_EQ(oakcommon_videoparams_get_is_valid(p, &v), OAKCOMMON_OK);
EXPECT_EQ(v, 0);
oakcommon_videoparams_free(p);
oakcommon_videoparams_free(&p);
}
TEST(CommonVideoParamsCApi, FreeNull)
@@ -48,10 +48,10 @@ TEST(CommonVideoParamsCApi, FreeNull)
TEST(CommonVideoParamsCApi, InitBasic)
{
OakCommonVideoParams *p = oakcommon_videoparams_init_basic(
OakVideoParams p = oakcommon_videoparams_init_basic(
1920, 1080, OAKCOMMON_PIXEL_FORMAT_U8,
4, 1, 1, OAKCOMMON_VIDEO_INTERLACE_NONE, 2);
ASSERT_NE(p, nullptr);
ASSERT_NE(p.ctx, nullptr);
int v;
EXPECT_EQ(oakcommon_videoparams_get_width(p, &v), OAKCOMMON_OK);
@@ -91,15 +91,15 @@ TEST(CommonVideoParamsCApi, InitBasic)
EXPECT_EQ(oakcommon_videoparams_get_buffer_size(p, &v), OAKCOMMON_OK);
EXPECT_EQ(v, 1920 * 1080 * 4);
oakcommon_videoparams_free(p);
oakcommon_videoparams_free(&p);
}
TEST(CommonVideoParamsCApi, InitWithTimeBase)
{
OakCommonVideoParams *p = oakcommon_videoparams_init_with_time_base(
OakVideoParams p = oakcommon_videoparams_init_with_time_base(
1280, 720, 1001, 30000, OAKCOMMON_PIXEL_FORMAT_F32, 4, 1, 1,
OAKCOMMON_VIDEO_INTERLACE_NONE, 1);
ASSERT_NE(p, nullptr);
ASSERT_NE(p.ctx, nullptr);
int num, den;
EXPECT_EQ(oakcommon_videoparams_get_time_base(p, &num, &den),
@@ -117,13 +117,13 @@ TEST(CommonVideoParamsCApi, InitWithTimeBase)
EXPECT_EQ(num, 1001);
EXPECT_EQ(den, 30000);
oakcommon_videoparams_free(p);
oakcommon_videoparams_free(&p);
}
TEST(CommonVideoParamsCApi, ScalarSetters)
{
OakCommonVideoParams *p = oakcommon_videoparams_init();
ASSERT_NE(p, nullptr);
OakVideoParams p = oakcommon_videoparams_init();
ASSERT_NE(p.ctx, nullptr);
int v;
EXPECT_EQ(oakcommon_videoparams_set_width(p, 640), OAKCOMMON_OK);
@@ -205,7 +205,7 @@ TEST(CommonVideoParamsCApi, ScalarSetters)
EXPECT_EQ(oakcommon_videoparams_get_effective_depth(p, &v), OAKCOMMON_OK);
EXPECT_EQ(v, 1);
oakcommon_videoparams_free(p);
oakcommon_videoparams_free(&p);
}
TEST(CommonVideoParamsCApi, NullHandleErrors)
@@ -214,123 +214,123 @@ TEST(CommonVideoParamsCApi, NullHandleErrors)
float f;
int64_t i64;
char buf[16];
EXPECT_EQ(oakcommon_videoparams_get_width(nullptr, &i),
EXPECT_EQ(oakcommon_videoparams_get_width(OakVideoParams{}, &i),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_set_width(nullptr, 1),
EXPECT_EQ(oakcommon_videoparams_set_width(OakVideoParams{}, 1),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_get_height(nullptr, &i),
EXPECT_EQ(oakcommon_videoparams_get_height(OakVideoParams{}, &i),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_set_height(nullptr, 1),
EXPECT_EQ(oakcommon_videoparams_set_height(OakVideoParams{}, 1),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_get_depth(nullptr, &i),
EXPECT_EQ(oakcommon_videoparams_get_depth(OakVideoParams{}, &i),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_set_depth(nullptr, 1),
EXPECT_EQ(oakcommon_videoparams_set_depth(OakVideoParams{}, 1),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_get_is_3d(nullptr, &i),
EXPECT_EQ(oakcommon_videoparams_get_is_3d(OakVideoParams{}, &i),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_get_time_base(nullptr, &i, &i),
EXPECT_EQ(oakcommon_videoparams_get_time_base(OakVideoParams{}, &i, &i),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_set_time_base(nullptr, 1, 1),
EXPECT_EQ(oakcommon_videoparams_set_time_base(OakVideoParams{}, 1, 1),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_get_frame_rate(nullptr, &i, &i),
EXPECT_EQ(oakcommon_videoparams_get_frame_rate(OakVideoParams{}, &i, &i),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_set_frame_rate(nullptr, 1, 1),
EXPECT_EQ(oakcommon_videoparams_set_frame_rate(OakVideoParams{}, 1, 1),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_frame_rate_as_time_base(nullptr, &i, &i),
EXPECT_EQ(oakcommon_videoparams_frame_rate_as_time_base(OakVideoParams{}, &i, &i),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_get_pixel_aspect_ratio(nullptr, &i, &i),
EXPECT_EQ(oakcommon_videoparams_get_pixel_aspect_ratio(OakVideoParams{}, &i, &i),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_set_pixel_aspect_ratio(nullptr, 1, 1),
EXPECT_EQ(oakcommon_videoparams_set_pixel_aspect_ratio(OakVideoParams{}, 1, 1),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_get_format(nullptr, &i),
EXPECT_EQ(oakcommon_videoparams_get_format(OakVideoParams{}, &i),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_set_format(nullptr, 0),
EXPECT_EQ(oakcommon_videoparams_set_format(OakVideoParams{}, 0),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_get_channel_count(nullptr, &i),
EXPECT_EQ(oakcommon_videoparams_get_channel_count(OakVideoParams{}, &i),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_set_channel_count(nullptr, 1),
EXPECT_EQ(oakcommon_videoparams_set_channel_count(OakVideoParams{}, 1),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_get_interlacing(nullptr, &i),
EXPECT_EQ(oakcommon_videoparams_get_interlacing(OakVideoParams{}, &i),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_set_interlacing(nullptr, 0),
EXPECT_EQ(oakcommon_videoparams_set_interlacing(OakVideoParams{}, 0),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_get_divider(nullptr, &i),
EXPECT_EQ(oakcommon_videoparams_get_divider(OakVideoParams{}, &i),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_set_divider(nullptr, 1),
EXPECT_EQ(oakcommon_videoparams_set_divider(OakVideoParams{}, 1),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_get_enabled(nullptr, &i),
EXPECT_EQ(oakcommon_videoparams_get_enabled(OakVideoParams{}, &i),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_set_enabled(nullptr, 1),
EXPECT_EQ(oakcommon_videoparams_set_enabled(OakVideoParams{}, 1),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_get_x(nullptr, &f), OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_set_x(nullptr, 0.0f), OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_get_y(nullptr, &f), OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_set_y(nullptr, 0.0f), OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_get_stream_index(nullptr, &i),
EXPECT_EQ(oakcommon_videoparams_get_x(OakVideoParams{}, &f), OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_set_x(OakVideoParams{}, 0.0f), OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_get_y(OakVideoParams{}, &f), OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_set_y(OakVideoParams{}, 0.0f), OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_get_stream_index(OakVideoParams{}, &i),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_set_stream_index(nullptr, 0),
EXPECT_EQ(oakcommon_videoparams_set_stream_index(OakVideoParams{}, 0),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_get_video_type(nullptr, &i),
EXPECT_EQ(oakcommon_videoparams_get_video_type(OakVideoParams{}, &i),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_set_video_type(nullptr, 0),
EXPECT_EQ(oakcommon_videoparams_set_video_type(OakVideoParams{}, 0),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_get_start_time(nullptr, &i64),
EXPECT_EQ(oakcommon_videoparams_get_start_time(OakVideoParams{}, &i64),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_set_start_time(nullptr, 0),
EXPECT_EQ(oakcommon_videoparams_set_start_time(OakVideoParams{}, 0),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_get_duration(nullptr, &i64),
EXPECT_EQ(oakcommon_videoparams_get_duration(OakVideoParams{}, &i64),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_set_duration(nullptr, 0),
EXPECT_EQ(oakcommon_videoparams_set_duration(OakVideoParams{}, 0),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_get_premultiplied_alpha(nullptr, &i),
EXPECT_EQ(oakcommon_videoparams_get_premultiplied_alpha(OakVideoParams{}, &i),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_set_premultiplied_alpha(nullptr, 0),
EXPECT_EQ(oakcommon_videoparams_set_premultiplied_alpha(OakVideoParams{}, 0),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_get_color_range(nullptr, &i),
EXPECT_EQ(oakcommon_videoparams_get_color_range(OakVideoParams{}, &i),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_set_color_range(nullptr, 0),
EXPECT_EQ(oakcommon_videoparams_set_color_range(OakVideoParams{}, 0),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_get_color_primaries(nullptr, &i),
EXPECT_EQ(oakcommon_videoparams_get_color_primaries(OakVideoParams{}, &i),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_set_color_primaries(nullptr, 0),
EXPECT_EQ(oakcommon_videoparams_set_color_primaries(OakVideoParams{}, 0),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_get_color_transfer(nullptr, &i),
EXPECT_EQ(oakcommon_videoparams_get_color_transfer(OakVideoParams{}, &i),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_set_color_transfer(nullptr, 0),
EXPECT_EQ(oakcommon_videoparams_set_color_transfer(OakVideoParams{}, 0),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_get_colorspace(nullptr, buf, sizeof(buf)),
EXPECT_EQ(oakcommon_videoparams_get_colorspace(OakVideoParams{}, buf, sizeof(buf)),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_set_colorspace(nullptr, "x"),
EXPECT_EQ(oakcommon_videoparams_set_colorspace(OakVideoParams{}, "x"),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_get_square_pixel_width(nullptr, &i),
EXPECT_EQ(oakcommon_videoparams_get_square_pixel_width(OakVideoParams{}, &i),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_get_effective_width(nullptr, &i),
EXPECT_EQ(oakcommon_videoparams_get_effective_width(OakVideoParams{}, &i),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_get_effective_height(nullptr, &i),
EXPECT_EQ(oakcommon_videoparams_get_effective_height(OakVideoParams{}, &i),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_get_effective_depth(nullptr, &i),
EXPECT_EQ(oakcommon_videoparams_get_effective_depth(OakVideoParams{}, &i),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_get_is_valid(nullptr, &i),
EXPECT_EQ(oakcommon_videoparams_get_is_valid(OakVideoParams{}, &i),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_get_bytes_per_channel(nullptr, &i),
EXPECT_EQ(oakcommon_videoparams_get_bytes_per_channel(OakVideoParams{}, &i),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_get_bytes_per_pixel(nullptr, &i),
EXPECT_EQ(oakcommon_videoparams_get_bytes_per_pixel(OakVideoParams{}, &i),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_get_buffer_size(nullptr, &i),
EXPECT_EQ(oakcommon_videoparams_get_buffer_size(OakVideoParams{}, &i),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_get_time_in_timebase_units(nullptr, 0, 1,
EXPECT_EQ(oakcommon_videoparams_get_time_in_timebase_units(OakVideoParams{}, 0, 1,
&i64),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_load_xml(nullptr, "<a/>"),
EXPECT_EQ(oakcommon_videoparams_load_xml(OakVideoParams{}, "<a/>"),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_save_xml(nullptr, buf, sizeof(buf)),
EXPECT_EQ(oakcommon_videoparams_save_xml(OakVideoParams{}, buf, sizeof(buf)),
OAKCOMMON_E_INVALID);
}
TEST(CommonVideoParamsCApi, NullOutParamErrors)
{
OakCommonVideoParams *p = oakcommon_videoparams_init();
ASSERT_NE(p, nullptr);
OakVideoParams p = oakcommon_videoparams_init();
ASSERT_NE(p.ctx, nullptr);
EXPECT_EQ(oakcommon_videoparams_get_width(p, nullptr),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_get_time_base(p, nullptr, nullptr),
@@ -340,13 +340,13 @@ TEST(CommonVideoParamsCApi, NullOutParamErrors)
EXPECT_EQ(oakcommon_videoparams_load_xml(p, nullptr), OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_load_xml(p, "not xml"),
OAKCOMMON_E_FAILED);
oakcommon_videoparams_free(p);
oakcommon_videoparams_free(&p);
}
TEST(CommonVideoParamsCApi, Colorspace)
{
OakCommonVideoParams *p = oakcommon_videoparams_init();
ASSERT_NE(p, nullptr);
OakVideoParams p = oakcommon_videoparams_init();
ASSERT_NE(p.ctx, nullptr);
EXPECT_EQ(oakcommon_videoparams_set_colorspace(p, "rec709"),
OAKCOMMON_OK);
int needed = oakcommon_videoparams_get_colorspace(p, nullptr, 0);
@@ -355,15 +355,15 @@ TEST(CommonVideoParamsCApi, Colorspace)
EXPECT_EQ(oakcommon_videoparams_get_colorspace(p, buf.data(), needed),
needed);
EXPECT_STREQ(buf.data(), "rec709");
oakcommon_videoparams_free(p);
oakcommon_videoparams_free(&p);
}
TEST(CommonVideoParamsCApi, TimeInTimebaseUnits)
{
OakCommonVideoParams *p = oakcommon_videoparams_init_with_time_base(
OakVideoParams p = oakcommon_videoparams_init_with_time_base(
1920, 1080, 1, 25, OAKCOMMON_PIXEL_FORMAT_U8, 4, 1, 1,
OAKCOMMON_VIDEO_INTERLACE_NONE, 1);
ASSERT_NE(p, nullptr);
ASSERT_NE(p.ctx, nullptr);
int64_t ts = -1;
EXPECT_EQ(oakcommon_videoparams_get_time_in_timebase_units(p, 2, 1, &ts),
@@ -371,26 +371,26 @@ TEST(CommonVideoParamsCApi, TimeInTimebaseUnits)
EXPECT_EQ(ts, 50); // 2 seconds at 25 fps
// Without a time base the result is AV_NOPTS_VALUE
OakCommonVideoParams *q = oakcommon_videoparams_init();
ASSERT_NE(q, nullptr);
OakVideoParams q = oakcommon_videoparams_init();
ASSERT_NE(q.ctx, nullptr);
EXPECT_EQ(oakcommon_videoparams_get_time_in_timebase_units(q, 2, 1, &ts),
OAKCOMMON_OK);
EXPECT_EQ(ts, INT64_MIN);
oakcommon_videoparams_free(p);
oakcommon_videoparams_free(q);
oakcommon_videoparams_free(&p);
oakcommon_videoparams_free(&q);
}
TEST(CommonVideoParamsCApi, Equals)
{
OakCommonVideoParams *a = oakcommon_videoparams_init_basic(
OakVideoParams a = oakcommon_videoparams_init_basic(
1920, 1080, OAKCOMMON_PIXEL_FORMAT_U8, 4, 1, 1,
OAKCOMMON_VIDEO_INTERLACE_NONE, 1);
OakCommonVideoParams *b = oakcommon_videoparams_init_basic(
OakVideoParams b = oakcommon_videoparams_init_basic(
1920, 1080, OAKCOMMON_PIXEL_FORMAT_U8, 4, 1, 1,
OAKCOMMON_VIDEO_INTERLACE_NONE, 1);
ASSERT_NE(a, nullptr);
ASSERT_NE(b, nullptr);
ASSERT_NE(a.ctx, nullptr);
ASSERT_NE(b.ctx, nullptr);
int equal = 0;
EXPECT_EQ(oakcommon_videoparams_equals(a, b, &equal), OAKCOMMON_OK);
@@ -400,23 +400,23 @@ TEST(CommonVideoParamsCApi, Equals)
EXPECT_EQ(oakcommon_videoparams_equals(a, b, &equal), OAKCOMMON_OK);
EXPECT_EQ(equal, 0);
EXPECT_EQ(oakcommon_videoparams_equals(nullptr, b, &equal),
EXPECT_EQ(oakcommon_videoparams_equals(OakVideoParams{}, b, &equal),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_equals(a, nullptr, &equal),
EXPECT_EQ(oakcommon_videoparams_equals(a, OakVideoParams{}, &equal),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_videoparams_equals(a, b, nullptr),
OAKCOMMON_E_INVALID);
oakcommon_videoparams_free(a);
oakcommon_videoparams_free(b);
oakcommon_videoparams_free(&a);
oakcommon_videoparams_free(&b);
}
TEST(CommonVideoParamsCApi, XmlRoundTrip)
{
OakCommonVideoParams *p = oakcommon_videoparams_init_with_time_base(
OakVideoParams p = oakcommon_videoparams_init_with_time_base(
1920, 1080, 1, 25, OAKCOMMON_PIXEL_FORMAT_F16, 4, 4, 3,
OAKCOMMON_VIDEO_INTERLACED_BOTTOM_FIRST, 2);
ASSERT_NE(p, nullptr);
ASSERT_NE(p.ctx, nullptr);
EXPECT_EQ(oakcommon_videoparams_set_colorspace(p, "rec709"),
OAKCOMMON_OK);
EXPECT_EQ(oakcommon_videoparams_set_color_primaries(p, 9), OAKCOMMON_OK);
@@ -426,8 +426,8 @@ TEST(CommonVideoParamsCApi, XmlRoundTrip)
std::vector<char> buf(needed);
ASSERT_EQ(oakcommon_videoparams_save_xml(p, buf.data(), needed), needed);
OakCommonVideoParams *q = oakcommon_videoparams_init();
ASSERT_NE(q, nullptr);
OakVideoParams q = oakcommon_videoparams_init();
ASSERT_NE(q.ctx, nullptr);
ASSERT_EQ(oakcommon_videoparams_load_xml(q, buf.data()), OAKCOMMON_OK);
int equal = 0;
@@ -450,8 +450,8 @@ TEST(CommonVideoParamsCApi, XmlRoundTrip)
EXPECT_EQ(oakcommon_videoparams_get_color_primaries(q, &v), OAKCOMMON_OK);
EXPECT_EQ(v, 9);
oakcommon_videoparams_free(p);
oakcommon_videoparams_free(q);
oakcommon_videoparams_free(&p);
oakcommon_videoparams_free(&q);
}
TEST(CommonVideoParamsCApi, StaticHelpers)
+50 -52
View File
@@ -29,8 +29,8 @@
namespace
{
std::string read_string(int (*fn)(OakCommonXmlReader *, char *, int),
OakCommonXmlReader *reader)
std::string read_string(int (*fn)(OakXmlReader, char *, int),
OakXmlReader reader)
{
int needed = fn(reader, nullptr, 0);
EXPECT_GT(needed, 0);
@@ -43,7 +43,7 @@ std::string read_string(int (*fn)(OakCommonXmlReader *, char *, int),
TEST(CommonXmlUtilsCApi, ReaderInitNullData)
{
EXPECT_EQ(oakcommon_xml_reader_init(nullptr), nullptr);
EXPECT_EQ(oakcommon_xml_reader_init(nullptr).ctx, nullptr);
}
TEST(CommonXmlUtilsCApi, ReaderFreeNull)
@@ -53,9 +53,8 @@ TEST(CommonXmlUtilsCApi, ReaderFreeNull)
TEST(CommonXmlUtilsCApi, ReadNextStartElement)
{
OakCommonXmlReader *r =
oakcommon_xml_reader_init("<root><child>value</child></root>");
ASSERT_NE(r, nullptr);
OakXmlReader r = oakcommon_xml_reader_init("<root><child>value</child></root>");
ASSERT_NE(r.ctx, nullptr);
int found = 0;
EXPECT_EQ(oakcommon_xml_reader_read_next_start_element(r, &found),
@@ -68,26 +67,26 @@ TEST(CommonXmlUtilsCApi, ReadNextStartElement)
EXPECT_EQ(found, 1);
EXPECT_EQ(read_string(oakcommon_xml_reader_name, r), "child");
oakcommon_xml_reader_free(r);
oakcommon_xml_reader_free(&r);
}
TEST(CommonXmlUtilsCApi, ReadNextStartElementNullHandle)
{
int found = 0;
EXPECT_EQ(oakcommon_xml_reader_read_next_start_element(nullptr, &found),
EXPECT_EQ(oakcommon_xml_reader_read_next_start_element(OakXmlReader{}, &found),
OAKCOMMON_E_INVALID);
OakCommonXmlReader *r = oakcommon_xml_reader_init("<root/>");
ASSERT_NE(r, nullptr);
OakXmlReader r = oakcommon_xml_reader_init("<root/>");
ASSERT_NE(r.ctx, nullptr);
EXPECT_EQ(oakcommon_xml_reader_read_next_start_element(r, nullptr),
OAKCOMMON_E_INVALID);
oakcommon_xml_reader_free(r);
oakcommon_xml_reader_free(&r);
}
TEST(CommonXmlUtilsCApi, ReadNextStartElementReturnsFalseAtEnd)
{
OakCommonXmlReader *r = oakcommon_xml_reader_init("<root/>");
ASSERT_NE(r, nullptr);
OakXmlReader r = oakcommon_xml_reader_init("<root/>");
ASSERT_NE(r.ctx, nullptr);
int found = -1;
EXPECT_EQ(oakcommon_xml_reader_read_next_start_element(r, &found),
@@ -97,21 +96,21 @@ TEST(CommonXmlUtilsCApi, ReadNextStartElementReturnsFalseAtEnd)
OAKCOMMON_OK);
EXPECT_EQ(found, 0);
oakcommon_xml_reader_free(r);
oakcommon_xml_reader_free(&r);
}
TEST(CommonXmlUtilsCApi, NameNullHandle)
{
char buf[16];
EXPECT_EQ(oakcommon_xml_reader_name(nullptr, buf, sizeof(buf)),
EXPECT_EQ(oakcommon_xml_reader_name(OakXmlReader{}, buf, sizeof(buf)),
OAKCOMMON_E_INVALID);
}
TEST(CommonXmlUtilsCApi, ReadElementText)
{
OakCommonXmlReader *r =
OakXmlReader r =
oakcommon_xml_reader_init("<root><child>a &amp; b</child></root>");
ASSERT_NE(r, nullptr);
ASSERT_NE(r.ctx, nullptr);
int found = 0;
EXPECT_EQ(oakcommon_xml_reader_read_next_start_element(r, &found),
@@ -122,22 +121,22 @@ TEST(CommonXmlUtilsCApi, ReadElementText)
EXPECT_EQ(read_string(oakcommon_xml_reader_read_element_text, r),
"a & b");
oakcommon_xml_reader_free(r);
oakcommon_xml_reader_free(&r);
}
TEST(CommonXmlUtilsCApi, ReadElementTextNullHandle)
{
char buf[16];
EXPECT_EQ(oakcommon_xml_reader_read_element_text(nullptr, buf,
EXPECT_EQ(oakcommon_xml_reader_read_element_text(OakXmlReader{}, buf,
sizeof(buf)),
OAKCOMMON_E_INVALID);
}
TEST(CommonXmlUtilsCApi, SkipCurrentElement)
{
OakCommonXmlReader *r = oakcommon_xml_reader_init(
OakXmlReader r = oakcommon_xml_reader_init(
"<root><unknown><nested/></unknown><known/></root>");
ASSERT_NE(r, nullptr);
ASSERT_NE(r.ctx, nullptr);
int found = 0;
EXPECT_EQ(oakcommon_xml_reader_read_next_start_element(r, &found),
@@ -153,20 +152,20 @@ TEST(CommonXmlUtilsCApi, SkipCurrentElement)
EXPECT_EQ(found, 1);
EXPECT_EQ(read_string(oakcommon_xml_reader_name, r), "known");
oakcommon_xml_reader_free(r);
oakcommon_xml_reader_free(&r);
}
TEST(CommonXmlUtilsCApi, SkipCurrentElementNullHandle)
{
EXPECT_EQ(oakcommon_xml_reader_skip_current_element(nullptr),
EXPECT_EQ(oakcommon_xml_reader_skip_current_element(OakXmlReader{}),
OAKCOMMON_E_INVALID);
}
TEST(CommonXmlUtilsCApi, Attributes)
{
OakCommonXmlReader *r = oakcommon_xml_reader_init(
OakXmlReader r = oakcommon_xml_reader_init(
"<root><item id=\"7\" name=\"a&quot;b\"/></root>");
ASSERT_NE(r, nullptr);
ASSERT_NE(r.ctx, nullptr);
int found = 0;
EXPECT_EQ(oakcommon_xml_reader_read_next_start_element(r, &found),
@@ -191,16 +190,16 @@ TEST(CommonXmlUtilsCApi, Attributes)
0);
EXPECT_STREQ(buf, "a\"b");
oakcommon_xml_reader_free(r);
oakcommon_xml_reader_free(&r);
}
TEST(CommonXmlUtilsCApi, AttributeErrorPaths)
{
EXPECT_EQ(oakcommon_xml_reader_attribute_count(nullptr, nullptr),
EXPECT_EQ(oakcommon_xml_reader_attribute_count(OakXmlReader{}, nullptr),
OAKCOMMON_E_INVALID);
OakCommonXmlReader *r = oakcommon_xml_reader_init("<root a=\"1\"/>");
ASSERT_NE(r, nullptr);
OakXmlReader r = oakcommon_xml_reader_init("<root a=\"1\"/>");
ASSERT_NE(r.ctx, nullptr);
int found = 0;
EXPECT_EQ(oakcommon_xml_reader_read_next_start_element(r, &found),
OAKCOMMON_OK);
@@ -210,30 +209,29 @@ TEST(CommonXmlUtilsCApi, AttributeErrorPaths)
OAKCOMMON_E_NOT_FOUND);
EXPECT_EQ(oakcommon_xml_reader_attribute_value(r, -1, buf, sizeof(buf)),
OAKCOMMON_E_NOT_FOUND);
EXPECT_EQ(oakcommon_xml_reader_attribute_name(nullptr, 0, buf,
EXPECT_EQ(oakcommon_xml_reader_attribute_name(OakXmlReader{}, 0, buf,
sizeof(buf)),
OAKCOMMON_E_INVALID);
oakcommon_xml_reader_free(r);
oakcommon_xml_reader_free(&r);
}
TEST(CommonXmlUtilsCApi, HasError)
{
OakCommonXmlReader *bad =
oakcommon_xml_reader_init("<root><unclosed></root>");
ASSERT_NE(bad, nullptr);
OakXmlReader bad = oakcommon_xml_reader_init("<root><unclosed></root>");
ASSERT_NE(bad.ctx, nullptr);
int has_error = 0;
EXPECT_EQ(oakcommon_xml_reader_has_error(bad, &has_error), OAKCOMMON_OK);
EXPECT_EQ(has_error, 1);
oakcommon_xml_reader_free(bad);
oakcommon_xml_reader_free(&bad);
OakCommonXmlReader *good = oakcommon_xml_reader_init("<root/>");
ASSERT_NE(good, nullptr);
OakXmlReader good = oakcommon_xml_reader_init("<root/>");
ASSERT_NE(good.ctx, nullptr);
EXPECT_EQ(oakcommon_xml_reader_has_error(good, &has_error), OAKCOMMON_OK);
EXPECT_EQ(has_error, 0);
EXPECT_EQ(oakcommon_xml_reader_has_error(nullptr, &has_error),
EXPECT_EQ(oakcommon_xml_reader_has_error(OakXmlReader{}, &has_error),
OAKCOMMON_E_INVALID);
oakcommon_xml_reader_free(good);
oakcommon_xml_reader_free(&good);
}
TEST(CommonXmlUtilsCApi, WriterFreeNull)
@@ -244,17 +242,17 @@ TEST(CommonXmlUtilsCApi, WriterFreeNull)
TEST(CommonXmlUtilsCApi, WriterNullHandleAndArgs)
{
char buf[16];
EXPECT_EQ(oakcommon_xml_writer_write_start_element(nullptr, "a"),
EXPECT_EQ(oakcommon_xml_writer_write_start_element(OakXmlWriter{}, "a"),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_xml_writer_write_end_element(nullptr),
EXPECT_EQ(oakcommon_xml_writer_write_end_element(OakXmlWriter{}),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_xml_writer_write_end_document(nullptr),
EXPECT_EQ(oakcommon_xml_writer_write_end_document(OakXmlWriter{}),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_xml_writer_output(nullptr, buf, sizeof(buf)),
EXPECT_EQ(oakcommon_xml_writer_output(OakXmlWriter{}, buf, sizeof(buf)),
OAKCOMMON_E_INVALID);
OakCommonXmlWriter *w = oakcommon_xml_writer_init();
ASSERT_NE(w, nullptr);
OakXmlWriter w = oakcommon_xml_writer_init();
ASSERT_NE(w.ctx, nullptr);
EXPECT_EQ(oakcommon_xml_writer_write_start_element(w, nullptr),
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_xml_writer_write_attribute(w, "a", nullptr),
@@ -263,13 +261,13 @@ TEST(CommonXmlUtilsCApi, WriterNullHandleAndArgs)
OAKCOMMON_E_INVALID);
EXPECT_EQ(oakcommon_xml_writer_write_text_element(w, nullptr, "x"),
OAKCOMMON_E_INVALID);
oakcommon_xml_writer_free(w);
oakcommon_xml_writer_free(&w);
}
TEST(CommonXmlUtilsCApi, WriterRoundTrip)
{
OakCommonXmlWriter *w = oakcommon_xml_writer_init();
ASSERT_NE(w, nullptr);
OakXmlWriter w = oakcommon_xml_writer_init();
ASSERT_NE(w.ctx, nullptr);
EXPECT_EQ(oakcommon_xml_writer_write_start_element(w, "root"),
OAKCOMMON_OK);
@@ -287,11 +285,11 @@ TEST(CommonXmlUtilsCApi, WriterRoundTrip)
ASSERT_GT(needed, 0);
std::vector<char> buf(needed);
EXPECT_EQ(oakcommon_xml_writer_output(w, buf.data(), needed), needed);
oakcommon_xml_writer_free(w);
oakcommon_xml_writer_free(&w);
// Read the produced document back.
OakCommonXmlReader *r = oakcommon_xml_reader_init(buf.data());
ASSERT_NE(r, nullptr);
OakXmlReader r = oakcommon_xml_reader_init(buf.data());
ASSERT_NE(r.ctx, nullptr);
int has_error = 1;
EXPECT_EQ(oakcommon_xml_reader_has_error(r, &has_error), OAKCOMMON_OK);
EXPECT_EQ(has_error, 0);
@@ -326,5 +324,5 @@ TEST(CommonXmlUtilsCApi, WriterRoundTrip)
OAKCOMMON_OK);
EXPECT_EQ(found, 0);
oakcommon_xml_reader_free(r);
oakcommon_xml_reader_free(&r);
}
+1 -1
View File
@@ -1,6 +1,6 @@
add_subdirectory(src)
add_subdirectory(c_api)
add_subdirectory(wrappers)
if(BUILD_TESTS)
add_subdirectory(tests)
endif()
+15 -13
View File
@@ -28,13 +28,6 @@
#include "colortransform.h"
#include "project.h"
// Same handle-echo pattern as sequence.cpp: oakcommon defines
// `struct OakCommonColorTransform { olive::ColorTransform impl; }`
// (src/common/c_api/colortransform.cpp) without exporting the definition.
struct OakCommonColorTransform {
olive::ColorTransform impl;
};
struct OakNodeColorManager {
olive::ColorManager impl;
};
@@ -349,21 +342,30 @@ int oaknode_colormanager_get_default_luma_coefs(OakNodeColorManager *manager,
}
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)
{
if (!manager || !transform || !out) {
if (!manager || !out) {
return OAKNODE_E_INVALID;
}
const olive::ColorTransform *native =
oakcommon_colortransform_get_native(transform);
if (!native) {
return OAKNODE_E_INVALID;
}
if (!has_config(&manager->impl)) {
return OAKNODE_E_STATE;
}
try {
*out = new OakCommonColorTransform{
manager->impl.get_compliant_color_space(transform->impl,
force_display != 0)};
const olive::ColorTransform compliant =
manager->impl.get_compliant_color_space(*native,
force_display != 0);
*out = oakcommon_colortransform_init_from_native(&compliant);
} catch (...) {
return OAKNODE_E_NOMEM;
}
if (!out->ctx) {
return OAKNODE_E_NOMEM;
}
return OAKNODE_OK;
}
+15 -14
View File
@@ -28,15 +28,6 @@
#include "project/sequence/sequence.h"
#include "videoparams.h"
// oakcommon defines its handle as `struct OakCommonVideoParams {
// olive::VideoParams impl; }` (src/common/c_api/videoparams.cpp) without
// exporting the definition. Echoing the identical layout here is the only
// way to hand native VideoParams values across without a field-by-field
// copy; keep in sync with oakcommon (flagged in the family-C report).
struct OakCommonVideoParams {
olive::VideoParams impl;
};
namespace
{
@@ -244,7 +235,7 @@ int oaknode_sequence_get_audio_stream_count(OakNodeSequence *sequence,
}
int oaknode_sequence_get_video_params(OakNodeSequence *sequence, int index,
OakCommonVideoParams **out)
OakVideoParams *out)
{
if (!sequence || !out || index < 0) {
return OAKNODE_E_INVALID;
@@ -253,24 +244,34 @@ int oaknode_sequence_get_video_params(OakNodeSequence *sequence, int index,
return OAKNODE_E_NOT_FOUND;
}
try {
*out = new OakCommonVideoParams{impl(sequence)->get_video_params(index)};
const olive::VideoParams params =
impl(sequence)->get_video_params(index);
*out = oakcommon_videoparams_init_from_native(&params);
} catch (...) {
return OAKNODE_E_NOMEM;
}
if (!out->ctx) {
return OAKNODE_E_NOMEM;
}
return OAKNODE_OK;
}
int oaknode_sequence_set_video_params(OakNodeSequence *sequence, int index,
const OakCommonVideoParams *params)
OakVideoParams params)
{
if (!sequence || !params || index < 0) {
if (!sequence || index < 0) {
return OAKNODE_E_INVALID;
}
const olive::VideoParams *native =
oakcommon_videoparams_get_native(params);
if (!native) {
return OAKNODE_E_INVALID;
}
if (index >= impl(sequence)->get_video_stream_count()) {
return OAKNODE_E_NOT_FOUND;
}
try {
impl(sequence)->set_video_params(params->impl, index);
impl(sequence)->set_video_params(*native, index);
} catch (...) {
return OAKNODE_E_FAILED;
}
@@ -24,9 +24,7 @@
#include <cmath>
#include <iostream>
#include "ocioutils.h"
#include "project.h"
#include "render/colorprocessor.h"
#include "sliderdisplaytype.h"
namespace olive
@@ -36,23 +34,23 @@ namespace olive
// GradingPrimaryTransform; do not rename them. OCIO's log style maps the
// classic wheels as: brightness = lift, contrast = gain, gamma = gamma.
const std::string OCIOGradingTransformLogNode::k_lift_input =
"ocio_grading_primary_brightness";
"OCIO_NAMESPACE_grading_primary_brightness";
const std::string OCIOGradingTransformLogNode::k_gain_input =
"ocio_grading_primary_contrast";
"OCIO_NAMESPACE_grading_primary_contrast";
const std::string OCIOGradingTransformLogNode::k_gamma_input =
"ocio_grading_primary_gamma";
"OCIO_NAMESPACE_grading_primary_gamma";
const std::string OCIOGradingTransformLogNode::k_saturation_input =
"ocio_grading_primary_saturation";
"OCIO_NAMESPACE_grading_primary_saturation";
const std::string OCIOGradingTransformLogNode::k_pivot_input =
"ocio_grading_primary_pivot";
"OCIO_NAMESPACE_grading_primary_pivot";
const std::string OCIOGradingTransformLogNode::k_clamp_black_enable_input =
"clamp_black_enable_in";
const std::string OCIOGradingTransformLogNode::k_clamp_black_input =
"ocio_grading_primary_clampBlack";
"OCIO_NAMESPACE_grading_primary_clampBlack";
const std::string OCIOGradingTransformLogNode::k_clamp_white_enable_input =
"clamp_white_enable_in";
const std::string OCIOGradingTransformLogNode::k_clamp_white_input =
"ocio_grading_primary_clampWhite";
"OCIO_NAMESPACE_grading_primary_clampWhite";
#define super OCIOBaseNode
@@ -75,7 +73,7 @@ OCIOGradingTransformLogNode::OCIOGradingTransformLogNode()
set_input_property(k_saturation_input, "min", 0.0);
add_input(k_pivot_input, NodeValue::k_float,
-0.2); // Default for GRADING_LOG listed in ocio::GradingPrimary
-0.2); // Default for GRADING_LOG listed in OCIO_NAMESPACE::GradingPrimary
set_input_property(k_pivot_input, "base", 0.01);
add_input(k_clamp_black_enable_input, NodeValue::k_boolean, false);
@@ -93,7 +91,7 @@ OCIOGradingTransformLogNode::OCIOGradingTransformLogNode()
set_input_property(k_clamp_white_input, "base", 0.01);
// Constrain the white clamp minimum to just above the (static) black clamp
// as per ocio::GradingPrimary::validate. When the black clamp is keyframed
// as per OCIO_NAMESPACE::GradingPrimary::validate. When the black clamp is keyframed
// or connected, Value() enforces the invariant per frame instead.
update_clamp_white_minimum();
}
@@ -105,7 +103,7 @@ std::string OCIOGradingTransformLogNode::name() const
std::string OCIOGradingTransformLogNode::id() const
{
return "org.olivevideoeditor.Olive.ociogradingtransformlog";
return "org.olivevideoeditor.Olive.OCIO_NAMESPACEgradingtransformlog";
}
std::vector<Node::CategoryID> OCIOGradingTransformLogNode::category() const
@@ -147,7 +145,7 @@ void OCIOGradingTransformLogNode::InputValueChangedEvent(const std::string &inpu
get_standard_value(k_clamp_black_enable_input).to_bool());
} else if (input == k_clamp_black_input) {
// Ensure the white clamp is always greater than the black clamp as per
// ocio::GradingPrimary::validate
// OCIO_NAMESPACE::GradingPrimary::validate
update_clamp_white_minimum();
}
@@ -192,15 +190,15 @@ void OCIOGradingTransformLogNode::update_clamp_white_minimum()
void OCIOGradingTransformLogNode::generate_processor()
{
if (manager()) {
ocio::GradingPrimaryTransformRcPtr gp =
ocio::GradingPrimaryTransform::Create(ocio::GRADING_LOG);
OCIO_NAMESPACE::GradingPrimaryTransformRcPtr gp =
OCIO_NAMESPACE::GradingPrimaryTransform::Create(OCIO_NAMESPACE::GRADING_LOG);
gp->makeDynamic();
gp->setDirection(ocio::TransformDirection::TRANSFORM_DIR_FORWARD);
gp->setDirection(OCIO_NAMESPACE::TransformDirection::TRANSFORM_DIR_FORWARD);
try {
set_processor(ColorProcessor::create(
manager()->get_config()->getProcessor(gp)));
} catch (const ocio::Exception &e) {
} catch (const OCIO_NAMESPACE::Exception &e) {
std::cerr << std::endl << e.what() << std::endl;
}
}
@@ -222,7 +220,7 @@ void OCIOGradingTransformLogNode::value(const NodeValueRow &value,
// OCIO expects vec3s on the GPU but RGBMs (master + RGB) on the
// CPU; the per-style master combination below mirrors
// ocio::GradingPrimary. Lift is additive, gain/gamma multiply.
// OCIO_NAMESPACE::GradingPrimary. Lift is additive, gain/gamma multiply.
Vector4D lift = value.at(k_lift_input).to_vec4();
lift.set_y(lift.y() + lift.x());
lift.set_z(lift.z() + lift.x());
@@ -250,18 +248,18 @@ void OCIOGradingTransformLogNode::value(const NodeValueRow &value,
if (!value.at(k_clamp_black_enable_input).to_bool()) {
job.insert(k_clamp_black_input,
NodeValue(NodeValue::k_float,
ocio::GradingPrimary::NoClampBlack()));
OCIO_NAMESPACE::GradingPrimary::NoClampBlack()));
}
if (!value.at(k_clamp_white_enable_input).to_bool()) {
job.insert(k_clamp_white_input,
NodeValue(NodeValue::k_float,
ocio::GradingPrimary::NoClampWhite()));
OCIO_NAMESPACE::GradingPrimary::NoClampWhite()));
}
if (value.at(k_clamp_black_enable_input).to_bool() &&
value.at(k_clamp_white_enable_input).to_bool()) {
// ocio::GradingPrimary::validate requires the white clamp to be
// OCIO_NAMESPACE::GradingPrimary::validate requires the white clamp to be
// greater than the black clamp. Keyframed or connected values
// can violate this at arbitrary times, so enforce the invariant
// per frame here.
+1 -2
View File
@@ -22,8 +22,6 @@
#ifndef OAK_NODE_H
#define OAK_NODE_H
#include "ofxhImageEffectAPI.h"
#include <algorithm>
#include <cstdint>
#include <list>
@@ -37,6 +35,7 @@
#include "keyframe.h"
#include "inputimmediate.h"
#include "param.h"
#include "ofxhImageEffectAPI.h"
#include "olive/core/util/timerange.h"
#include "render/audioplaybackcache.h"
#include "render/audiowaveformcache.h"
+6 -4
View File
@@ -116,10 +116,12 @@ target_include_directories(oakrender PUBLIC
/opt/homebrew/include/OpenEXR
)
foreach(t oakrender oakgl oakvulkan)
target_link_options(${t} PRIVATE
"-undefined" "dynamic_lookup"
)
foreach(t oakrender oakgl oakgl2 oakvulkan)
if(TARGET ${t})
target_link_options(${t} PRIVATE
"-undefined" "dynamic_lookup"
)
endif()
endforeach()
target_link_libraries(oakrender PRIVATE
+7 -7
View File
@@ -263,15 +263,15 @@ TEST_F(ColorManagerTest, CompliantColorTransform)
get_string(oaknode_colormanager_get_default_display, m);
ASSERT_FALSE(display.empty());
OakCommonColorTransform *t = oakcommon_colortransform_init_display(
OakColorTransform t = oakcommon_colortransform_init_display(
display.c_str(), "No Such View", "");
ASSERT_NE(t, nullptr);
ASSERT_NE(t.ctx, nullptr);
OakCommonColorTransform *compliant = nullptr;
OakColorTransform compliant = {};
ASSERT_EQ(oaknode_colormanager_get_compliant_color_transform(m, t, 0,
&compliant),
OAKNODE_OK);
ASSERT_NE(compliant, nullptr);
ASSERT_NE(compliant.ctx, nullptr);
int is_display = 0;
ASSERT_EQ(oakcommon_colortransform_is_display(compliant, &is_display),
@@ -286,11 +286,11 @@ TEST_F(ColorManagerTest, CompliantColorTransform)
needed);
EXPECT_STRNE(buf.data(), "No Such View");
oakcommon_colortransform_free(compliant);
oakcommon_colortransform_free(t);
oakcommon_colortransform_free(&compliant);
oakcommon_colortransform_free(&t);
// Error paths
EXPECT_EQ(oaknode_colormanager_get_compliant_color_transform(m, nullptr, 0,
EXPECT_EQ(oaknode_colormanager_get_compliant_color_transform(m, OakColorTransform{}, 0,
&compliant),
OAKNODE_E_INVALID);
+17 -14
View File
@@ -247,31 +247,34 @@ TEST(SequenceTest, VideoParamsRoundTrip)
ASSERT_GE(count, 1);
// Default slot is readable
OakCommonVideoParams *params = nullptr;
OakVideoParams params = {};
ASSERT_EQ(oaknode_sequence_get_video_params(seq, 0, &params), OAKNODE_OK);
ASSERT_NE(params, nullptr);
oakcommon_videoparams_free(params);
ASSERT_NE(params.ctx, nullptr);
oakcommon_videoparams_free(&params);
// Out of range
EXPECT_EQ(oaknode_sequence_get_video_params(seq, count, &params),
OAKNODE_E_NOT_FOUND);
EXPECT_EQ(oaknode_sequence_set_video_params(seq, count, nullptr),
EXPECT_EQ(oaknode_sequence_set_video_params(seq, count, OakVideoParams{}),
OAKNODE_E_INVALID);
// Replace with explicit 1920x1080 @ 25fps params
OakCommonVideoParams *replacement = oakcommon_videoparams_init_with_time_base(
1920, 1080, 1, 25, 0 /*pixel_format*/, 4 /*nb_channels*/, 1, 1,
OAKCOMMON_VIDEO_INTERLACE_NONE, 1);
ASSERT_NE(replacement, nullptr);
OakVideoParams replacement =
oakcommon_videoparams_init_with_time_base(
1920, 1080, 1, 25, 0 /*pixel_format*/, 4 /*nb_channels*/, 1, 1,
OAKCOMMON_VIDEO_INTERLACE_NONE, 1);
ASSERT_NE(replacement.ctx, nullptr);
ASSERT_EQ(oaknode_sequence_set_video_params(seq, 0, replacement),
OAKNODE_OK);
oakcommon_videoparams_free(replacement);
oakcommon_videoparams_free(&replacement);
OakCommonVideoParams *readback = nullptr;
ASSERT_EQ(oaknode_sequence_get_video_params(seq, 0, &readback), OAKNODE_OK);
ASSERT_NE(readback, nullptr);
OakVideoParams readback = {};
ASSERT_EQ(oaknode_sequence_get_video_params(seq, 0, &readback),
OAKNODE_OK);
ASSERT_NE(readback.ctx, nullptr);
int width = 0, height = 0, tb_num = 0, tb_den = 0;
ASSERT_EQ(oakcommon_videoparams_get_width(readback, &width), OAKCOMMON_OK);
ASSERT_EQ(oakcommon_videoparams_get_width(readback, &width),
OAKCOMMON_OK);
ASSERT_EQ(oakcommon_videoparams_get_height(readback, &height),
OAKCOMMON_OK);
ASSERT_EQ(oakcommon_videoparams_get_time_base(readback, &tb_num, &tb_den),
@@ -279,7 +282,7 @@ TEST(SequenceTest, VideoParamsRoundTrip)
EXPECT_EQ(width, 1920);
EXPECT_EQ(height, 1080);
expect_rational(tb_num, tb_den, 1, 25);
oakcommon_videoparams_free(readback);
oakcommon_videoparams_free(&readback);
oaknode_sequence_free(seq);
}
View File
+1
View File
@@ -1,6 +1,7 @@
target_sources(oakrender PRIVATE
renderer.cpp
cache.cpp
cancelatom.cpp
color.cpp
manager.cpp
)
+135
View File
@@ -0,0 +1,135 @@
/***
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 "../../../include/render/cancelatom.h"
#include <atomic>
#include <cstdint>
#include "alivecount.h"
#include "cancelatom.h"
namespace
{
/**
* @brief Heap box behind every OakCancelAtom's ctx pointer.
*
* Holds the wrapped CancelAtom plus its atomic reference count. addref
* and release are emitted in this translation unit so the function
* pointers stored in a handle always run code from the DLL that created
* the object.
*/
struct CancelAtomBox {
olive::CancelAtom impl;
std::atomic<uint32_t> refs;
CancelAtomBox()
: refs(1)
{
}
};
CancelAtomBox *box(OakCancelAtom atom)
{
return static_cast<CancelAtomBox *>(atom.ctx);
}
olive::CancelAtom *impl(OakCancelAtom atom)
{
auto *b = box(atom);
return b ? &b->impl : nullptr;
}
/**
* @brief Handle addref thunk: atomically increments the count.
*/
void cancel_atom_addref(void *ctx)
{
auto *b = static_cast<CancelAtomBox *>(ctx);
if (b)
b->refs.fetch_add(1, std::memory_order_relaxed);
}
/**
* @brief Handle release thunk: decrements the count, destroys at zero.
*/
void cancel_atom_release(void *ctx)
{
auto *b = static_cast<CancelAtomBox *>(ctx);
if (b && b->refs.fetch_sub(1, std::memory_order_acq_rel) == 1) {
delete b;
oakrender_c_api::alive_dec();
}
}
} // namespace
OakCancelAtom oakrender_cancelatom_init(void)
{
OakCancelAtom h = {};
try {
h.ctx = new CancelAtomBox();
} catch (...) {
h.ctx = nullptr;
}
h.addref = &cancel_atom_addref;
h.release = &cancel_atom_release;
h.abi_version = OAKRENDER_ABI_VERSION;
if (h.ctx)
oakrender_c_api::alive_inc();
return h;
}
void oakrender_cancelatom_free(OakCancelAtom *atom)
{
if (!atom || !atom->ctx || !atom->release)
return;
atom->release(atom->ctx);
atom->ctx = nullptr;
}
int oakrender_cancelatom_cancel(OakCancelAtom atom)
{
auto *c = impl(atom);
if (!c)
return OAKRENDER_E_INVALID;
c->cancel();
return OAKRENDER_OK;
}
int oakrender_cancelatom_is_cancelled(OakCancelAtom atom, int *cancelled)
{
auto *c = impl(atom);
if (!c || !cancelled)
return OAKRENDER_E_INVALID;
*cancelled = c->is_cancelled() ? 1 : 0;
return OAKRENDER_OK;
}
int oakrender_cancelatom_heard_cancel(OakCancelAtom atom, int *heard)
{
auto *c = impl(atom);
if (!c || !heard)
return OAKRENDER_E_INVALID;
*heard = c->heard_cancel() ? 1 : 0;
return OAKRENDER_OK;
}
+30 -10
View File
@@ -30,6 +30,7 @@
#include "color/colormanager/colormanager.h"
#include "filefunctions.h"
#include <OpenColorIO/OpenColorIO.h>
namespace
{
@@ -57,7 +58,7 @@ OakColorProcessor *oakrender_color_processor_create(const char *src_space,
return nullptr;
}
try {
ocio::ConstConfigRcPtr config = olive::ColorManager::get_default_config();
OCIO_NAMESPACE::ConstConfigRcPtr config = olive::ColorManager::get_default_config();
if (!config) {
return nullptr;
}
@@ -69,17 +70,17 @@ OakColorProcessor *oakrender_color_processor_create(const char *src_space,
src = config->getCanonicalName(src_space);
}
// OCIO failures are non-fatal (matching the C++ behavior): the
// OCIO_NAMESPACE failures are non-fatal (matching the C++ behavior): the
// handle is still returned, but holds a null processor and
// conversions pass through.
ocio::ConstProcessorRcPtr processor;
OCIO_NAMESPACE::ConstProcessorRcPtr processor;
try {
if (direction == OAKRENDER_COLOR_DIRECTION_NORMAL) {
processor = config->getProcessor(src.c_str(), dst_transform);
} else {
processor = config->getProcessor(dst_transform, src.c_str());
}
} catch (ocio::Exception &) {
} catch (OCIO_NAMESPACE::Exception &) {
processor = nullptr;
}
@@ -169,7 +170,7 @@ int oakrender_color_manager_display_transform(const char *display,
return OAKRENDER_E_INVALID;
}
try {
ocio::ConstConfigRcPtr config = olive::ColorManager::get_default_config();
OCIO_NAMESPACE::ConstConfigRcPtr config = olive::ColorManager::get_default_config();
if (!config) {
return OAKRENDER_E_STATE;
}
@@ -197,25 +198,44 @@ int oakrender_color_manager_display_transform(const char *display,
}
// Source = the config's reference colorspace (role lookup).
ocio::ConstColorSpaceRcPtr ref_cs =
config->getColorSpace(ocio::ROLE_REFERENCE);
OCIO_NAMESPACE::ConstColorSpaceRcPtr ref_cs =
config->getColorSpace(OCIO_NAMESPACE::ROLE_REFERENCE);
if (!ref_cs) {
return OAKRENDER_E_STATE;
}
auto dvt = ocio::DisplayViewTransform::Create();
auto dvt = OCIO_NAMESPACE::DisplayViewTransform::Create();
dvt->setSrc(ref_cs->getName());
dvt->setDisplay(display);
dvt->setView(view);
ocio::ConstProcessorRcPtr processor = config->getProcessor(dvt);
OCIO_NAMESPACE::ConstProcessorRcPtr processor = config->getProcessor(dvt);
if (!processor) {
return OAKRENDER_E_NOT_FOUND;
}
return write_string(processor->getCacheID(), buf, n);
} catch (ocio::Exception &) {
} catch (OCIO_NAMESPACE::Exception &) {
return OAKRENDER_E_NOT_FOUND;
} catch (...) {
return OAKRENDER_E_FAILED;
}
}
int oakrender_color_processor_convert_frame(OakColorProcessor *processor,
OakCodecFrame *frame)
{
if (!processor || !processor->ptr || !frame || !frame->ptr) {
return OAKRENDER_E_INVALID;
}
try {
// In-place: ColorProcessor::convert_frame() applies the CPU
// processor to the frame's pixel buffer through an
// OCIO::PackedImageDesc view. A processor whose underlying OCIO
// processor is null (creation failure was non-fatal) is a
// pass-through and still reports success, mirroring the C++ API.
processor->ptr->convert_frame(frame->ptr);
return OAKRENDER_OK;
} catch (...) {
return OAKRENDER_E_FAILED;
}
}
+2 -2
View File
@@ -30,9 +30,9 @@ add_library(oakrender SHARED ${OAKRENDER_SOURCES})
# Dynamically loaded render backends (oak_renderer_* C ABI). Loaded via
# dlopen by DynamicRenderer from the app render_backends/ dir.
add_library(oakgl SHARED opengl/openglbackend_c.cpp)
add_library(oakgl2 SHARED opengl/openglbackend_c.cpp)
add_library(oakvulkan SHARED vulkan/vulkanbackend_c.cpp)
foreach(backend oakgl oakvulkan)
foreach(backend oakgl2 oakvulkan)
target_link_libraries(${backend} PRIVATE oakrender)
endforeach()
+5 -4
View File
@@ -134,6 +134,11 @@ void ColorProcessor::convert_frame(Frame *f)
cpu_processor_->apply(img);
}
ocio::ConstProcessorRcPtr ColorProcessor::get_processor()
{
return processor_;
}
Color ColorProcessor::convert_color(const Color &in)
{
if (!cpu_processor_) {
@@ -163,10 +168,6 @@ ColorProcessorPtr ColorProcessor::create(ocio::ConstProcessorRcPtr processor)
return std::make_shared<ColorProcessor>(processor);
}
ocio::ConstProcessorRcPtr ColorProcessor::get_processor()
{
return processor_;
}
void ColorProcessor::convert_frame(FramePtr f)
{
+6 -4
View File
@@ -116,10 +116,12 @@ endif()
# Symbols of the not-yet-split engine modules (codec/audio/task/config/
# pluginSupport/...) dangle by design. The backend libraries resolve most
# symbols from liboakrender at load time and dangle the same way.
foreach(t oakrender oakgl oakvulkan)
target_link_options(${t} PRIVATE
"-undefined" "dynamic_lookup"
)
foreach(t oakrender oakgl oakgl2 oakvulkan)
if(TARGET ${t})
target_link_options(${t} PRIVATE
"-undefined" "dynamic_lookup"
)
endif()
endforeach()
target_link_libraries(oakrender PRIVATE
+1
View File
@@ -17,6 +17,7 @@ endif()
add_executable(oakrender-gtest
cache_test.cpp
cancelatom_test.cpp
color_test.cpp
manager_test.cpp
renderer_test.cpp
+136
View File
@@ -0,0 +1,136 @@
/***
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/>.
***/
// Same-dir quoted include would hit the src/node/transition/render/
// bridge header (olive::CancelAtom) first on this build's include path;
// reference the public header relative to this file instead.
#include "../../../include/render/cancelatom.h"
#include <gtest/gtest.h>
#include "render/cache.h" /* oakrender_debug_alive_count */
TEST(OakCancelAtomTest, InitFree)
{
const int alive_before = oakrender_debug_alive_count();
OakCancelAtom atom = oakrender_cancelatom_init();
ASSERT_NE(atom.ctx, nullptr);
EXPECT_NE(atom.addref, nullptr);
EXPECT_NE(atom.release, nullptr);
EXPECT_EQ(atom.abi_version, OAKRENDER_ABI_VERSION);
EXPECT_EQ(oakrender_debug_alive_count(), alive_before + 1);
oakrender_cancelatom_free(&atom);
EXPECT_EQ(atom.ctx, nullptr);
EXPECT_EQ(oakrender_debug_alive_count(), alive_before);
// NULL and empty handles are no-ops
oakrender_cancelatom_free(nullptr);
oakrender_cancelatom_free(&atom);
EXPECT_EQ(oakrender_debug_alive_count(), alive_before);
}
TEST(OakCancelAtomTest, CancelStateMachine)
{
OakCancelAtom atom = oakrender_cancelatom_init();
ASSERT_NE(atom.ctx, nullptr);
int flag = -1;
EXPECT_EQ(oakrender_cancelatom_is_cancelled(atom, &flag), OAKRENDER_OK);
EXPECT_EQ(flag, 0);
EXPECT_EQ(oakrender_cancelatom_cancel(atom), OAKRENDER_OK);
// Cancel must not be heard until a consumer reads the flag
int heard = -1;
EXPECT_EQ(oakrender_cancelatom_heard_cancel(atom, &heard), OAKRENDER_OK);
EXPECT_EQ(heard, 0);
EXPECT_EQ(oakrender_cancelatom_is_cancelled(atom, &flag), OAKRENDER_OK);
EXPECT_EQ(flag, 1);
EXPECT_EQ(oakrender_cancelatom_heard_cancel(atom, &heard), OAKRENDER_OK);
EXPECT_EQ(heard, 1);
oakrender_cancelatom_free(&atom);
}
TEST(OakCancelAtomTest, InvalidArgs)
{
OakCancelAtom empty = {};
int flag = 7;
EXPECT_EQ(oakrender_cancelatom_cancel(empty), OAKRENDER_E_INVALID);
EXPECT_EQ(oakrender_cancelatom_is_cancelled(empty, &flag),
OAKRENDER_E_INVALID);
EXPECT_EQ(flag, 7);
EXPECT_EQ(oakrender_cancelatom_heard_cancel(empty, &flag),
OAKRENDER_E_INVALID);
EXPECT_EQ(flag, 7);
OakCancelAtom atom = oakrender_cancelatom_init();
ASSERT_NE(atom.ctx, nullptr);
EXPECT_EQ(oakrender_cancelatom_is_cancelled(atom, nullptr),
OAKRENDER_E_INVALID);
EXPECT_EQ(oakrender_cancelatom_heard_cancel(atom, nullptr),
OAKRENDER_E_INVALID);
oakrender_cancelatom_free(&atom);
}
TEST(OakCancelAtomTest, AddrefReleaseCountSemantics)
{
const int alive_before = oakrender_debug_alive_count();
OakCancelAtom atom = oakrender_cancelatom_init();
ASSERT_NE(atom.ctx, nullptr);
// Copy the struct and take an extra reference; both copies share the
// same underlying object and cancel state
OakCancelAtom copy = atom;
copy.addref(copy.ctx);
EXPECT_EQ(oakrender_cancelatom_cancel(atom), OAKRENDER_OK);
int flag = 0;
EXPECT_EQ(oakrender_cancelatom_is_cancelled(copy, &flag), OAKRENDER_OK);
EXPECT_EQ(flag, 1);
// Releasing one reference keeps the object alive for the other
atom.release(atom.ctx);
EXPECT_EQ(oakrender_debug_alive_count(), alive_before + 1);
flag = 0;
EXPECT_EQ(oakrender_cancelatom_is_cancelled(copy, &flag), OAKRENDER_OK);
EXPECT_EQ(flag, 1);
// The final reference destroys the object
oakrender_cancelatom_free(&copy);
EXPECT_EQ(copy.ctx, nullptr);
EXPECT_EQ(oakrender_debug_alive_count(), alive_before);
}
TEST(OakCancelAtomTest, AddrefReleaseNullCtxIsSafe)
{
// NULL ctx must not crash the thunks
OakCancelAtom atom = oakrender_cancelatom_init();
ASSERT_NE(atom.ctx, nullptr);
atom.addref(nullptr);
atom.release(nullptr);
oakrender_cancelatom_free(&atom);
}