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:
@@ -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")
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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(¶ms);
|
||||
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(¶ms);
|
||||
EXPECT_EQ(enc.ctx, nullptr);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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(¶ms), 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, ¶ms, &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(¶ms), OAKCODEC_OK);
|
||||
|
||||
oakcodec_proxy_result result = {};
|
||||
ASSERT_EQ(oakcodec_proxy_get_or_start("/tmp/oakcodec_proxy_test",
|
||||
"some_video.mp4", 2, ¶ms, &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();
|
||||
}
|
||||
Reference in New Issue
Block a user