scaffold: CMake + API skeleton + smoke test #2

Merged
marfrit merged 1 commits from noether/repo-scaffold into main 2026-05-24 20:10:26 +00:00
7 changed files with 683 additions and 0 deletions
+15
View File
@@ -0,0 +1,15 @@
build/
build-*/
*.o
*.a
*.so
*.so.*
*.spv
.vscode/
.cache/
compile_commands.json
CMakeCache.txt
CMakeFiles/
cmake_install.cmake
Makefile
.ninja_*
+128
View File
@@ -0,0 +1,128 @@
# SPDX-License-Identifier: BSD-2-Clause
#
# daedalus-decoder — frame-level GPU H.264 decoder for V3D7 (Pi 5).
# Phase 1 scaffold; see DESIGN.md for architecture.
#
# Build dependencies:
# - daedalus-fourier ≥ 0.1.0 (kernel pack, V3D primitives + recipe layer)
# resolved via pkg-config; install via the daedalus-fourier upstream
# `cmake --install` rule (PR #5 made the .pc relocatable, so any
# install prefix works as long as $PKG_CONFIG_PATH is set).
# - Vulkan headers + libvulkan (pulled in transitively via
# daedalus-fourier, listed here explicitly for the link order).
#
# Build:
# cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release
# cmake --build build
# ctest --test-dir build
cmake_minimum_required(VERSION 3.20)
project(daedalus-decoder
VERSION 0.0.1
DESCRIPTION "Frame-level GPU H.264 decoder for Raspberry Pi 5 / V3D7"
LANGUAGES C)
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_C_EXTENSIONS OFF)
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Release)
endif()
# Pi 5 is the only supported target. Other aarch64 SoCs (Pi 4 V3D4,
# RK3588 Mali, …) might work but would need explicit substrate +
# shader-pack validation per the daedalus-fourier architecture
# backlog. Don't pretend to support what we haven't validated.
if(NOT CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64")
message(WARNING
"daedalus-decoder is designed for aarch64 (Pi 5 BCM2712 / V3D7). "
"Build will proceed but is unlikely to function.")
endif()
add_compile_options(-Wall -Wextra -Wno-unused-parameter)
# ---- Dependencies --------------------------------------------------
find_package(PkgConfig REQUIRED)
# daedalus-fourier — find_package via pkg-config per the Phase 1
# decision §9.6. Minimum version 0.1.0 (the cycle 6-9 shaders + pool
# + recipe-flip baseline). PKG_CONFIG_PATH should point at the
# directory holding daedalus-fourier.pc (e.g. /usr/local/lib/pkgconfig
# or a custom install prefix).
pkg_check_modules(DAEDALUS_FOURIER REQUIRED daedalus-fourier>=0.1.0)
# Vulkan — daedalus-fourier already depends on this; we add it
# explicitly so the link order stays correct (daedalus-fourier static
# archive contains undefined vk* symbols that the loader resolves).
find_package(Vulkan REQUIRED)
# ---- Version string baked into the library ------------------------
# git rev tagged onto the version string for traceability; degrades
# gracefully to bare semver if git isn't available.
execute_process(
COMMAND git -C ${CMAKE_CURRENT_SOURCE_DIR} rev-parse --short=7 HEAD
OUTPUT_VARIABLE DAEDALUS_DECODER_GITREV
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_QUIET)
if(DAEDALUS_DECODER_GITREV)
set(DAEDALUS_DECODER_VERSION "${PROJECT_VERSION}+g${DAEDALUS_DECODER_GITREV}")
else()
set(DAEDALUS_DECODER_VERSION "${PROJECT_VERSION}")
endif()
message(STATUS "daedalus-decoder version: ${DAEDALUS_DECODER_VERSION}")
# ---- Library ------------------------------------------------------
add_library(daedalus_decoder STATIC
src/daedalus_decoder.c
)
target_include_directories(daedalus_decoder
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
PRIVATE
src
${DAEDALUS_FOURIER_INCLUDE_DIRS}
)
target_link_directories(daedalus_decoder
PUBLIC
${DAEDALUS_FOURIER_LIBRARY_DIRS}
)
target_link_libraries(daedalus_decoder
PUBLIC
# Order matters: daedalus-fourier static archive references
# vulkan symbols; the loader needs daedalus-fourier first then
# vulkan to resolve them.
${DAEDALUS_FOURIER_LIBRARIES}
Vulkan::Vulkan
)
target_compile_definitions(daedalus_decoder
PRIVATE
DAEDALUS_DECODER_VERSION="${DAEDALUS_DECODER_VERSION}"
)
target_compile_options(daedalus_decoder PRIVATE -O2)
# ---- Smoke test ---------------------------------------------------
enable_testing()
add_executable(test_smoke tests/test_smoke.c)
target_link_libraries(test_smoke PRIVATE daedalus_decoder)
target_compile_options(test_smoke PRIVATE -O2)
add_test(NAME smoke COMMAND test_smoke)
# ---- Install ------------------------------------------------------
#
# Library + public header. Stage 2/3 will add a pkg-config file and
# CMake config exports once the API stabilises; pre-0.1 the scaffold
# install just gives the static archive a home.
include(GNUInstallDirs)
install(TARGETS daedalus_decoder
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})
install(FILES include/daedalus_decoder.h
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
+24
View File
@@ -0,0 +1,24 @@
BSD 2-Clause License
Copyright (c) 2026, Markus Fritsche
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+182
View File
@@ -0,0 +1,182 @@
/* SPDX-License-Identifier: BSD-2-Clause */
/*
* daedalus-decoder — public C API.
*
* Frame-level GPU H.264 decoder targeting V3D7 (Raspberry Pi 5). Built
* on daedalus-fourier's V3D compute primitives at frame granularity —
* one Vulkan submit per frame, one fence wait per frame, encoded
* bitstream in (via libavcodec's per-MB intercept), NV12 frame out.
*
* Per the 2026-05-24 Phase 1 design decisions:
* - libavcodec intercept is at macroblock-level (substitution-arc
* evolution): the caller is expected to drive the per-MB CABAC /
* CAVLC entropy decode and feed each macroblock's descriptor +
* coefficients via daedalus_decoder_append_mb(). flush_frame()
* builds the per-frame VkCommandBuffer and submits.
* - DPB is Vulkan-native VkImage with VK_KHR_external_memory_dma_buf
* export. The caller can obtain the output frame's dmabuf fd
* via daedalus_decoder_export_dmabuf().
* - Daemon integration shape: this library is statically linked into
* daedalus_v4l2_daemon. No IPC.
*
* STATUS: scaffold. No GPU pipeline implemented yet; all functions
* are stubs that compile but do not decode anything. See DESIGN.md
* for the architecture.
*
* ABI: pre-0.1 — every signature here may change. Don't rely on
* stability yet.
*/
#ifndef DAEDALUS_DECODER_H
#define DAEDALUS_DECODER_H
#include <stddef.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/* -------------------------------------------------------------------
* Opaque decoder context. One per concurrent stream.
* ----------------------------------------------------------------- */
typedef struct daedalus_decoder daedalus_decoder;
/* -------------------------------------------------------------------
* Per-macroblock input. Mirrors §3 of DESIGN.md. The caller's
* libavcodec intercept populates this from the H264SliceContext
* fields after ff_h264_decode_mb_cabac/cavlc returns and before
* ff_h264_hl_decode_mb is supposed to run (we replace the latter).
* ----------------------------------------------------------------- */
struct daedalus_decoder_mb_input {
/* Frame coordinates (macroblock units). */
uint16_t mb_x;
uint16_t mb_y;
/* Type + quantisation. */
uint8_t mb_type; /* H.264 spec table 7-13/7-14/7-17/7-18 enum */
uint8_t mb_qp_y;
uint8_t mb_qp_uv;
uint8_t cbp; /* coded block pattern, 0..47 */
/* Intra prediction (used iff mb_type == I_NxN or I_16x16). */
uint8_t intra_4x4_modes[16];
uint8_t intra_16x16_mode;
uint8_t intra_chroma_mode;
/* Inter motion / partitions (used iff P_* or B_*). */
uint8_t partition_mode; /* P_16x16 / P_16x8 / P_8x16 / P_8x8 / etc. */
int8_t ref_idx_l0[4]; /* per partition; -1 = not used */
int8_t ref_idx_l1[4]; /* B only */
int16_t mv_l0[4][2]; /* qpel precision (1/4 sample); (x, y) */
int16_t mv_l1[4][2];
/* Deblocking filter parameters. */
uint8_t deblock_disable; /* 0 = enabled */
int8_t deblock_alpha_c0;
int8_t deblock_beta;
/* Transform coefficients — 256 luma (4x4 x 16) + 64 cb + 64 cr,
* column-major within each 4x4 block (matches FFmpeg convention).
* Caller-owned; copied during append. */
const int16_t *coeffs; /* points at exactly 384 int16_t */
};
/* -------------------------------------------------------------------
* Output frame format selector.
* ----------------------------------------------------------------- */
typedef enum {
DAEDALUS_DECODER_OUTPUT_NV12 = 0, /* default; Stage 4 final */
DAEDALUS_DECODER_OUTPUT_RGBA = 1, /* Stage 5 opt-in */
} daedalus_decoder_output_format;
/* -------------------------------------------------------------------
* Lifecycle
* ----------------------------------------------------------------- */
/* Create a decoder context for the given **coded** frame dimensions.
*
* width, height: pixels of the H.264 coded picture, NOT the displayed
* picture. Both must be multiples of 16 (macroblock granularity).
* For displayed 1080p (1920×1080), the coded frame is 1920×1088 with
* the SPS's `frame_cropping_*` offsets cropping the bottom 8 rows.
* The caller is responsible for translating from SPS dims + crop
* rectangle to the values passed here; we decode the coded frame.
*
* Returns NULL on bad dimensions or allocation failure. Returns a
* usable context with daedalus_decoder_has_qpu() == 0 when Vulkan
* init fails — callers that need GPU work should check has_qpu
* before relying on it.
*/
daedalus_decoder *daedalus_decoder_create(int width, int height);
/* Free all resources. Safe with NULL. */
void daedalus_decoder_destroy(daedalus_decoder *dec);
/* Switch output format BEFORE the first append_mb call of a frame.
* Default is NV12. Returns 0 on success, -1 if called mid-frame
* (caller must flush first). */
int daedalus_decoder_set_output_format(daedalus_decoder *dec,
daedalus_decoder_output_format fmt);
/* -------------------------------------------------------------------
* Per-frame submission
* ----------------------------------------------------------------- */
/* Append one macroblock's data to the current frame's descriptor SSBO
* + coefficient SSBO. No GPU dispatch yet — just CPU-side writes.
*
* Must be called in raster order (mb_y * mb_width + mb_x) for the
* intra-prediction wavefront to work correctly in Phase 1.
*
* Returns 0 on success, negative on bounds violation or OOM.
*/
int daedalus_decoder_append_mb(daedalus_decoder *dec,
const struct daedalus_decoder_mb_input *mb);
/* End-of-frame flush: builds the per-frame VkCommandBuffer with all
* pipeline stages, submits once, waits on a single fence, copies the
* NV12 (or RGBA when opted in) output into the caller-provided
* planes.
*
* For NV12:
* out_y / y_stride: Y plane (W*H bytes minimum, at the given stride)
* out_uv / uv_stride: interleaved UV plane (W*(H/2) bytes minimum)
*
* For RGBA: out_y receives 4*W*H bytes at y_stride; out_uv ignored.
*
* Returns 0 on success, negative on Vulkan failure or undecodable
* frame. After return, the decoder is ready for the next frame's
* append calls.
*/
int daedalus_decoder_flush_frame(daedalus_decoder *dec,
uint8_t *out_y, size_t y_stride,
uint8_t *out_uv, size_t uv_stride);
/* Export the most-recently-decoded frame as a dma_buf fd. The fd is
* owned by the caller and must be closed when done. Lets V4L2
* consumers (daedalus_v4l2_daemon, libva-v4l2-request-fourier) attach
* the GPU-decoded surface directly to a CAPTURE plane without a CPU
* round-trip.
*
* Returns the dmabuf fd on success, -1 on failure. Must be called
* AFTER flush_frame returns for the relevant frame.
*/
int daedalus_decoder_export_dmabuf(daedalus_decoder *dec, int plane);
/* -------------------------------------------------------------------
* Diagnostics
* ----------------------------------------------------------------- */
/* daedalus-decoder build version (semver string, e.g. "0.0.1+g0a1b2c3"). */
const char *daedalus_decoder_version(void);
/* Whether the underlying daedalus-fourier context picked up a working
* V3D7 Vulkan instance. Returns 0 if Vulkan init failed and the
* decoder is operating in stub / failure mode. */
int daedalus_decoder_has_qpu(const daedalus_decoder *dec);
#ifdef __cplusplus
}
#endif
#endif /* DAEDALUS_DECODER_H */
+166
View File
@@ -0,0 +1,166 @@
/* SPDX-License-Identifier: BSD-2-Clause */
/*
* daedalus-decoder — public C API implementation.
*
* Scaffold only. Most functions return success with no GPU work
* performed; the bodies will fill in across Phases 1-4 per DESIGN.md
* §8. This file exists so the API surface compiles, links, and can
* be smoke-tested end-to-end (ctx create / append / flush / destroy)
* before any shader work begins.
*/
#include "internal.h"
#include <stdlib.h>
#include <string.h>
/* Built via -D from CMakeLists. */
#ifndef DAEDALUS_DECODER_VERSION
#define DAEDALUS_DECODER_VERSION "0.0.1+scaffold"
#endif
const char *daedalus_decoder_version(void)
{
return DAEDALUS_DECODER_VERSION;
}
daedalus_decoder *daedalus_decoder_create(int width, int height)
{
if (width <= 0 || height <= 0)
return NULL;
if ((width & 15) || (height & 15))
return NULL; /* must be multiple of 16 */
daedalus_decoder *dec = calloc(1, sizeof(*dec));
if (!dec)
return NULL;
dec->width = width;
dec->height = height;
dec->mb_width = width >> 4;
dec->mb_height = height >> 4;
dec->n_mbs = dec->mb_width * dec->mb_height;
dec->output_fmt = DAEDALUS_DECODER_OUTPUT_NV12;
/* daedalus-fourier ctx — required. Phase 1 needs the QPU; if
* Vulkan init fails the decoder is unusable. Caller can check
* via daedalus_decoder_has_qpu(). */
dec->dctx = daedalus_ctx_create();
if (!dec->dctx) {
free(dec);
return NULL;
}
dec->mb_descs = calloc((size_t) dec->n_mbs, sizeof(*dec->mb_descs));
dec->coeffs = calloc((size_t) dec->n_mbs * 384, sizeof(int16_t));
if (!dec->mb_descs || !dec->coeffs) {
daedalus_decoder_destroy(dec);
return NULL;
}
return dec;
}
void daedalus_decoder_destroy(daedalus_decoder *dec)
{
if (!dec)
return;
free(dec->coeffs);
free(dec->mb_descs);
if (dec->dctx)
daedalus_ctx_destroy(dec->dctx);
free(dec);
}
int daedalus_decoder_set_output_format(daedalus_decoder *dec,
daedalus_decoder_output_format fmt)
{
if (!dec)
return -1;
if (dec->mbs_appended != 0)
return -1; /* mid-frame change forbidden */
if (fmt != DAEDALUS_DECODER_OUTPUT_NV12 &&
fmt != DAEDALUS_DECODER_OUTPUT_RGBA)
return -1;
dec->output_fmt = fmt;
return 0;
}
int daedalus_decoder_append_mb(daedalus_decoder *dec,
const struct daedalus_decoder_mb_input *mb)
{
if (!dec || !mb || !mb->coeffs)
return -1;
if (mb->mb_x >= dec->mb_width || mb->mb_y >= dec->mb_height)
return -1;
/* Raster-order check — Phase 1's intra wavefront requires it.
* Caller is libavcodec's slice loop which produces raster order
* naturally, so this should never fire in practice. */
int expected = mb->mb_y * dec->mb_width + mb->mb_x;
if (expected != dec->mbs_appended)
return -1;
struct daedalus_decoder_mb_desc *d = &dec->mb_descs[expected];
d->mb_x = mb->mb_x;
d->mb_y = mb->mb_y;
d->mb_type = mb->mb_type;
d->mb_qp_y = mb->mb_qp_y;
d->mb_qp_uv = mb->mb_qp_uv;
d->cbp = mb->cbp;
memcpy(d->intra_4x4_modes, mb->intra_4x4_modes, 16);
d->intra_16x16_mode = mb->intra_16x16_mode;
d->intra_chroma_mode = mb->intra_chroma_mode;
d->partition_mode = mb->partition_mode;
memcpy(d->ref_idx_l0, mb->ref_idx_l0, 4);
memcpy(d->ref_idx_l1, mb->ref_idx_l1, 4);
memcpy(d->mv_l0, mb->mv_l0, sizeof(d->mv_l0));
memcpy(d->mv_l1, mb->mv_l1, sizeof(d->mv_l1));
d->deblock_disable = mb->deblock_disable;
d->deblock_alpha_c0 = mb->deblock_alpha_c0;
d->deblock_beta = mb->deblock_beta;
memcpy(&dec->coeffs[(size_t) expected * 384],
mb->coeffs,
384 * sizeof(int16_t));
dec->mbs_appended++;
return 0;
}
int daedalus_decoder_flush_frame(daedalus_decoder *dec,
uint8_t *out_y, size_t y_stride,
uint8_t *out_uv, size_t uv_stride)
{
if (!dec)
return -1;
if (dec->mbs_appended != dec->n_mbs)
return -1; /* incomplete frame */
/* TODO Phase 1: build VkCommandBuffer with the 4 (or 5) pipeline
* stages, vkQueueSubmit, wait on fence, copy out to the caller's
* planes (or dma_buf-export when caller uses the export API).
*
* Scaffold behaviour: zero the output planes so downstream
* consumers don't read uninitialised memory, then reset for the
* next frame. Returns -ENOSYS-equivalent (-2) so any test that
* expects real pixels notices. */
(void) out_y; (void) y_stride; (void) out_uv; (void) uv_stride;
dec->mbs_appended = 0;
return -2; /* not implemented */
}
int daedalus_decoder_export_dmabuf(daedalus_decoder *dec, int plane)
{
(void) dec; (void) plane;
/* TODO Phase 1: vkGetMemoryFdKHR on the DPB slot's VkImage memory. */
return -1;
}
int daedalus_decoder_has_qpu(const daedalus_decoder *dec)
{
if (!dec || !dec->dctx)
return 0;
return daedalus_ctx_has_qpu(dec->dctx);
}
+68
View File
@@ -0,0 +1,68 @@
/* SPDX-License-Identifier: BSD-2-Clause */
/*
* daedalus-decoder — internal types shared across translation units.
* Not installed; pure-internal.
*/
#ifndef DAEDALUS_DECODER_INTERNAL_H
#define DAEDALUS_DECODER_INTERNAL_H
#include "daedalus_decoder.h"
#include <stdint.h>
#include <stddef.h>
#include <daedalus.h> /* daedalus-fourier public API */
/* Per-MB descriptor as the GPU sees it. Bit-laid-out to match the
* shader's std430 layout. Kept narrow (32 bytes target) so a 1080p
* frame's 8160 entries fit in ~256 KiB SSBO.
*
* TODO once the shaders exist: nail down the exact std430 layout and
* static_assert sizeof / alignof here. */
struct daedalus_decoder_mb_desc {
uint16_t mb_x;
uint16_t mb_y;
uint8_t mb_type;
uint8_t mb_qp_y;
uint8_t mb_qp_uv;
uint8_t cbp;
uint8_t intra_4x4_modes[16];
uint8_t intra_16x16_mode;
uint8_t intra_chroma_mode;
uint8_t partition_mode;
uint8_t _pad0;
int8_t ref_idx_l0[4];
int8_t ref_idx_l1[4];
int16_t mv_l0[4][2];
int16_t mv_l1[4][2];
uint8_t deblock_disable;
int8_t deblock_alpha_c0;
int8_t deblock_beta;
uint8_t _pad1;
};
struct daedalus_decoder {
/* Geometry. */
int width;
int height;
int mb_width; /* width / 16 */
int mb_height; /* height / 16 */
int n_mbs;
/* daedalus-fourier context (Vulkan + V3D7 runner). */
daedalus_ctx *dctx;
/* Frame-shaped staging (CPU-side; will move to mapped SSBO once
* Vulkan plumbing is in place). */
struct daedalus_decoder_mb_desc *mb_descs; /* n_mbs */
int16_t *coeffs; /* n_mbs * 384 */
int mbs_appended; /* per-frame count */
/* Output format. */
daedalus_decoder_output_format output_fmt;
};
#endif /* DAEDALUS_DECODER_INTERNAL_H */
+100
View File
@@ -0,0 +1,100 @@
/* SPDX-License-Identifier: BSD-2-Clause */
/*
* Scaffold smoke test — verifies the daedalus-decoder library links
* cleanly against daedalus-fourier and the lifecycle entry points
* don't immediately crash. No actual decoding work yet.
*
* Returns 0 on success, non-zero on any unexpected behaviour.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "daedalus_decoder.h"
#define EXPECT(cond, msg) do { \
if (!(cond)) { \
fprintf(stderr, "EXPECT FAIL %s:%d: %s\n", __FILE__, __LINE__, msg); \
return 1; \
} \
} while (0)
int main(void)
{
printf("daedalus-decoder version: %s\n", daedalus_decoder_version());
/* Create / destroy null is a no-op. */
daedalus_decoder_destroy(NULL);
/* Bad dimensions rejected. */
EXPECT(daedalus_decoder_create(0, 0 ) == NULL, "zero dims must reject");
EXPECT(daedalus_decoder_create(1919, 1088) == NULL, "non-16-multiple width must reject");
EXPECT(daedalus_decoder_create(1920, 1079) == NULL, "non-16-multiple height must reject");
/* Valid 1088p create. */
daedalus_decoder *dec = daedalus_decoder_create(1920, 1088);
if (!dec) {
/* Vulkan init failure on this host — degrades to skip, not fail.
* (CI runners without V3D7 will hit this; the smoke test
* shouldn't gate on hardware presence.) */
fprintf(stderr, "SKIP: daedalus_decoder_create returned NULL "
"(Vulkan / V3D7 unavailable on this host)\n");
return 0;
}
printf("ctx created: 1920x1088, has_qpu=%d\n",
daedalus_decoder_has_qpu(dec));
/* set_output_format mid-frame on virgin ctx is allowed
* (mbs_appended == 0). */
EXPECT(daedalus_decoder_set_output_format(dec, DAEDALUS_DECODER_OUTPUT_RGBA) == 0,
"switch to RGBA on virgin ctx");
EXPECT(daedalus_decoder_set_output_format(dec, DAEDALUS_DECODER_OUTPUT_NV12) == 0,
"switch back to NV12");
/* Append rejects out-of-bounds + null inputs. */
int16_t coeffs[384] = {0};
struct daedalus_decoder_mb_input mb = {0};
mb.coeffs = coeffs;
mb.mb_x = 0; mb.mb_y = 0;
EXPECT(daedalus_decoder_append_mb(dec, NULL) == -1, "null mb rejects");
{
struct daedalus_decoder_mb_input mb2 = mb;
mb2.coeffs = NULL;
EXPECT(daedalus_decoder_append_mb(dec, &mb2) == -1, "null coeffs rejects");
}
{
struct daedalus_decoder_mb_input mb2 = mb;
mb2.mb_x = 9999; mb2.mb_y = 9999;
EXPECT(daedalus_decoder_append_mb(dec, &mb2) == -1, "OOB coords reject");
}
/* Append first MB at raster index 0 — should succeed. */
EXPECT(daedalus_decoder_append_mb(dec, &mb) == 0, "append (0,0)");
/* Skipping (0,1) and appending (1,0) violates raster order — reject. */
{
struct daedalus_decoder_mb_input mb2 = mb;
mb2.mb_x = 0; mb2.mb_y = 1;
EXPECT(daedalus_decoder_append_mb(dec, &mb2) == -1,
"out-of-raster-order rejects");
}
/* In-order: (1,0). */
mb.mb_x = 1; mb.mb_y = 0;
EXPECT(daedalus_decoder_append_mb(dec, &mb) == 0, "append (1,0)");
/* Flush an incomplete frame: should fail because mbs_appended != n_mbs. */
EXPECT(daedalus_decoder_flush_frame(dec, NULL, 0, NULL, 0) == -1,
"incomplete-frame flush rejects");
/* set_output_format mid-frame (mbs_appended > 0) must reject. */
EXPECT(daedalus_decoder_set_output_format(dec, DAEDALUS_DECODER_OUTPUT_RGBA) == -1,
"mid-frame format change rejects");
daedalus_decoder_destroy(dec);
printf("smoke OK\n");
return 0;
}