Skip to content

New api draft - rebased - #1610

Draft
Sakoram wants to merge 16 commits into
mainfrom
new-api-rebased
Draft

New api draft - rebased#1610
Sakoram wants to merge 16 commits into
mainfrom
new-api-rebased

Conversation

@Sakoram

@Sakoram Sakoram commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator

just a draft of how API could look like.
Next step: port fully RxTxApp, integration tests and plugins to run full test suite

notes:

  1. mtl_buffer_t is huge and users can have an issue understanding what to set there. consider hiding all fields behind void* and use using setters getters
  2. need to port destruction guard to API functions from current pipeline api.

Your Name added 16 commits June 10, 2026 14:13
Import the additive unified session API subsystem from new_api_draft:
- lib/src/new_api/ (mt_session*, video tx/rx, buffer, event, common)
- include/mtl_session_api.h public header
- app/sample/new_api/ samples
- meson wiring (subdir new_api, header install, sample executables)

Video ST20 only; audio/anc/st22 vtables are extern-declared but unused.
Verified: zero pipeline-symbol usage; builds clean against main with no
source changes (libmtl.so + 4 new samples link OK).
Re-apply the new_api_draft migration of the st20p RxTxApp paths on top of
main, routing frame get/put/create/destroy through mtl_session_* instead of
st20p_*. Preserve main's per-frame pkts modulo-gated dbg logging and the
app_rx_st20p_result wire-stats reconciliation block (ported to
mtl_session_io_stats_get). app_base.h st20p tx/rx structs now hold
mtl_session_t* session.

Deferred: ST20P_TX_FLAG_DROP_WHEN_LATE has no new-API flag yet (feature gap).
Add tests/unit/new_api/ harness + suite (St20NewApiRxTest) mirroring the
old-API pipeline suite (St20PipelineRxTest) so both the pipeline and the
unified-session RX paths have parallel coverage of the same producer/consumer
contract. Both suites pass (12/12); full UnitTest 268/268.

Characterization tests of existing imported code (Gate-2 failing-first
exempt). New-API adaptations vs pipeline: busy atomic -> removed (none in
new_api); corrupted counter -> incomplete-delivery assertion; stats overlay
-> abstract stats_get vs passthrough io_stats.
- tx: emit warn() when st20p drop_when_late is set, so the unsupported
  unified-session option is an observable no-op instead of a silent one
  (reviewer MAJOR).
- rx: hash s->st20p_frame_size in sha_check to match the TX side, instead
  of buf->data_size, so partial-recv / multi-plane frames stay symmetric
  (reviewer MINOR).
Add MTL_SESSION_FLAG_DROP_WHEN_LATE to the new unified session API for
st20 video TX. Raw st20_api.h has no ST20_TX_FLAG_DROP_WHEN_LATE, so the
late-drop is enforced in video_tx_get_next_frame: when the flag and
USER_PACING are set, a READY frame whose TAI timestamp is more than one
frame period in the past is recycled to FREE instead of transmitted, a
buffers_dropped stat is bumped, and an MTL_EVENT_FRAME_LATE event is
posted. Drops per call are capped at MT_VIDEO_TX_DROP_MAX_BATCH, matching
the pipeline's ST_TX_DROP_MAX_BATCH, so a backlog cannot stall the
datapath thread. The per-frame TAI is already stored in
st20_frames[].tv_meta by tx_apply_buffer_metadata, so no new plumbing is
needed.

Add a new-API TX unit harness/test pair mirroring the existing RX harness,
and an old-API st20 pipeline TX harness/test mirror so the two TX paths
are pinned by parallel suites (lifecycle, user_meta passthrough, derive
no-convert, stats surfaces, non-blocking get, framebuff count, and the
three drop-when-late cases). Both suites pass under UnitTest.
Expand the new-API (unified session) st20 video RX unit suite from the
6 stats-only cases to cover every mapped RX feature, and mirror the
feature cases on the old st20 pipeline RX suite so the two stay parallel.
Test-only change; no lib edits.

New St20NewApiRxTest cases (characterization of existing production code
in lib/src/new_api/mt_session_video_rx.c, driven via the harness #include
of the production .c — no NIC/EAL device):
  - StatusCompleteVsReconstructed: COMPLETE and RECONSTRUCTED both collapse
    onto MTL_FRAME_STATUS_COMPLETE; CORRUPTED -> INCOMPLETE + flag.
  - ConvertTransportToApp: derive is zero-copy (no converter); transport!=app
    runs the (stubbed) converter and the buffer carries the app fmt/size.
  - UserMetaPassthrough: frame_trans user_meta aliases into buf->user_meta.
  - VideoMetaFieldsFilled: pkts_total/pkts_recv[]/second_field/dimensions.
  - RtpTimestampAndTfmt: rtp_timestamp/tfmt/timestamp forwarded from meta.
  - FramebuffCntClampedToTwo: drives the real mtl_video_rx_session_init clamp
    via a stubbed st20_rx_create that captures ops.framebuff_cnt and fails.
  - BlockGetTimeoutSemantics: timeout_ms=0 on an empty ready_ring is a bounded
    poll returning -ETIMEDOUT, never a condvar block.
  - BuffersProcessedSiteCrossMode: pins divergence #4 — library-owned bumps
    buffers_processed at buffer_get, user-owned-post at notify_frame_ready.
  - AutoDetectPostsFormatEvent: video_rx_notify_detected posts
    MTL_EVENT_FORMAT_DETECTED with the detected geometry.
  - ExtFrameQueryWrapperBinds: mem_register + buffer_post + the query_ext_frame
    wrapper bind a posted user buffer to a transport ext_frame slot.

Harness (st20_rx_harness.{c,h}) gains: caller-supplied-meta injection, a
recorded video_convert_frame stub, an st20_rx_create capture stub for the
clamp, an event ring + poll wrapper, user-owned-post setup, and mem_register/
buffer_post/query_ext_frame wrappers.

Mirrored on St20PipelineRxTest (where a real old-pipeline analog exists):
StatusReconstructedPassthrough (raw status, no collapse), RtpTimestampAndTfmt,
VideoMetaFieldsFilled (pkts_total/second_field; pipeline copies no pkts_recv),
and UserMetaPassthrough (value-copied into the framebuf user_meta buffer). The
pipeline harness now injects caller-supplied meta and allocates per-framebuf
user_meta scratch.

Deferred to Phase 4 (integration), noted in the test file:
  - TimingParserPostsReport: MTL_EVENT_TIMING_REPORT is posted nowhere in
    lib/src/new_api; the timing-parser->event bridge is not yet wired, so
    there is no unit-tier production path to exercise.

All 300 UnitTest cases pass (26 across the two RX suites); clang-format-14
and build.sh clean.
BLOCKER 1: tx_frame_is_late treated an unstamped slot (timestamp==0,
tfmt==0==TAI) as infinitely late and silently dropped it. Gate the late
check on timestamp != 0 so only frames the user actually stamped with TAI
are eligible.

BLOCKER 2: the drop path recycled the slot and posted only FRAME_LATE, so
in MTL_BUFFER_USER_OWNED mode the external buffer (s->user_buf_ctx[i]) was
never returned and leaked. Mirror video_tx_notify_frame_done: retrieve and
clear user_buf_ctx[i] and post MTL_EVENT_BUFFER_DONE with user_ctx in
addition to FRAME_LATE.

Remove the unreachable MT_VIDEO_TX_DROP_MAX_BATCH cap (the for-loop is
already bounded by the framebuffer count; tripping it would have
transmitted a late frame) and the redundant frame_late.epoch_skipped=0.
…HEN_LATE

The flag now exists in the unified session API, so replace the placeholder
warn() no-op (added when the flag was absent) with the real flag mapping,
making drop-when-late reachable end-to-end through RxTxApp.
- mtl-build skill: add the Unit Tests build+run section (exact binary path
  build_unit/tests/unit/UnitTest), fix Format guidance to prefer formatting
  only touched files over the whole-tree format-coding.sh churn, and add the
  broken-jobserver + stale-.ninja_deps phantom-crash traps to Common Build Errors.
- copilot-instructions: orchestrator should independently re-run build + full
  unit suite after a Develop handoff; reported crashes are often stale-build
  artifacts, not regressions.
Three unit-provable crash/correctness fixes in the new unified session API:

- B5: size the buffer wrapper pool from the actual low-level frame count
  (forced >= 2), not the raw num_buffers. With num_buffers 0/1 the hot-path
  `buffers[idx % buffer_count]` divided by zero or aliased frames. Create now
  always calls mtl_session_init_buffers(), sized via mtl_session_video_frame_count().
- B6: video_tx_slice_ready() returned a fake success (0) for an unimplemented
  mode; return -ENOTSUP to match the RX twin and the dispatcher convention.
- RX-ext: external-frame completion stored the library's internal user_meta as
  the user ctx; store the app's ext-frame opaque (meta->opaque) instead.

Adds four RED-before/GREEN-after unit tests pinning each fix
(BufferPoolSizeFollowsFrameCount rx+tx, SliceReadyUnsupported,
ExtFrameCompletionCarriesAppOpaque). Full unit suite 309/309.
Redesign the event system to honor the producer/consumer model:

- Producer (tasklet via notify_* callbacks) now appends WITHOUT allocating
  or blocking: replace the malloc-per-post pointer ring with a value-backed
  rte_ring_create_elem(sizeof(mtl_event_t), RING_F_SC_DEQ). On a full ring
  it drops and bumps events_dropped (atomic), never waits. This removes the
  per-event mt_rte_zmalloc/mt_rte_free from tasklet context.
- Consumer (app thread) now BLOCKS on the eventfd via poll() honoring
  timeout_ms instead of a usleep(100) busy-wait. timeout==0 -> -ETIMEDOUT,
  stop() -> -EAGAIN (distinct, P7/P27), with the sub-ms wait rounded up to
  avoid a poll(...,0) spin.
- stop() now signals the eventfd (mtl_session_events_signal, async-signal-safe
  non-blocking write) so a consumer already blocked in poll() wakes promptly
  and returns -EAGAIN, instead of stalling for the full timeout.
- Implement get_event_fd on both video vtables; drop the now-dead pointer-mode
  fallback dequeue in the dispatcher.

Scope: event mechanism only. The BUFFER_DONE-per-frame flood, user-owned
conversion, and tasklet re-enqueue malloc are addressed in later Phase 2 steps.

Unit: 320/320 (11 new event/stop tests incl. a discriminating blocked-poll
stop-wakeup concurrency test). Reviewer: APPROVE.
In library-owned TX mode a completed transmission returns the frame slot to
the FREE pool implicitly and the app reuses it via the next buffer_get(); it
does not consume a per-frame completion event. video_tx_notify_frame_done was
nonetheless posting MTL_EVENT_BUFFER_DONE on every transmitted frame, flooding
the 64-entry value-backed event ring (nothing drains it at frame rate) and
dropping real events.

Gate the BUFFER_DONE post on s->ownership == MTL_BUFFER_USER_OWNED, mirroring
the existing drop_when_late path. Stats updates and the TX_FRAME_FREE store stay
unconditional; FRAME_LATE and RX are untouched. BUFFER_DONE.ctx (user_ctx) is
only meaningful for user-owned external buffers anyway.

Unit: 322/322. New tests: LibraryOwnedFrameDonePostsNoEvent (fails without fix),
UserOwnedFrameDonePostsBufferDone (distinct data/ctx sentinels assert ctx
round-trip). Reviewer: APPROVE.
…ser-buf ring (Phase 2.3, B3+B4)

User-owned (zero-copy) TX previously did, inside the pacing tasklet
(video_tx_get_next_frame):
  - a full-frame video_convert_frame for non-derive (format-mismatch) buffers,
  - per-buffer mt_rte_zmalloc/free for the user-buf ring entries (B3),
  - a re-enqueue malloc when no slot was free.

Both are data-plane violations (heavy work + allocation in the tasklet).

B3: make user_buf_ring value-backed via rte_ring_create_elem(sizeof(entry));
enqueue/dequeue by value, no per-buffer malloc/free. The ring is now touched
only by the app thread (buffer_post / uinit).

B4: move slot binding + conversion onto the app thread (buffer_post):
  - video_tx_claim_free_slot: CAS FREE->APP_OWNED.
  - video_tx_fill_slot: derive(st20_tx_set_ext_frame) or non-derive
    (video_convert_frame into the slot framebuffer), store user_ctx, then
    RELEASE-publish READY; on hard error release the slot to FREE.
  - buffer_post drains the backlog claim-then-pop (a slot is reserved before
    the head is dequeued, so the backlog is never reordered = strict FIFO at
    the bind stage); a new buffer goes to the tail when a backlog exists.
  - get_next_frame is now a pure READY->TRANSMITTING scan: no convert, no
    enqueue/dequeue, no malloc.

Safety: app thread claims a slot exclusively (APP_OWNED) before
st20_tx_set_ext_frame (which only touches that slot, refcnt==0) and before
RELEASE-publishing READY; the tasklet ACQUIRE-claims READY->TRANSMITTING, so
the converted framebuffer and user_ctx are fully visible.

Completion contract: buffer_post returning 0 yields exactly one async
BUFFER_DONE; a negative return yields none. Backlog entries (already accepted
with a 0 return) get a BUFFER_DONE on hard bind error so the app is not stranded.

Unit: 325/325 (new UserOwnedConvertHappensOnPostNotTasklet,
UserOwnedValueRingRoundTrip, UserOwnedPostBackpressureDefersAndBinds - the last
defers 3 entries, frees one slot, and asserts the OLDEST binds first; fails
against the prior head-rotation logic). Reviewer: APPROVE.

Known follow-up (Phase 4 lifecycle): no buffer_flush yet, so a backlog is
drained only by subsequent buffer_post; documented in the buffer_post contract.
Bring the new_API docs into the repo and align them with the current
post-Phase-2 implementation:

- CURRENT_STATE.md: rewritten to lib/src/new_api/ paths, value-backed
  event ring + eventfd poll, frame-state model, implemented buffer_post/
  mem_register, completion contract, and the -ENOTSUP/TODO list.
- GRACEFUL_SHUTDOWN.md: guard-centric teardown. destroy() is the single
  safe primitive (validated generation-tagged handle + refcount drain +
  eventfd wake); stop() is optional for SIGINT quiesce and pause/resume.
- List-of-changes.md: fixed dangling links, annotated auto-start divergence.
- samples/: reference the real app/sample/new_api samples; slice/ST22
  diagrams marked not-yet-implemented.

Docs describe the target guard design; the guard itself is not yet wired
into mt_session.c.
@Sakoram

Sakoram commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator Author
  1. needs better API to get buffer structure with app owned buffers. user needs a way to provide timestamps for example.
  2. consider adding 2 timestamps. one for user_pacing second for user_timestamp
  3. rethink synchronization of video and audio. use case: both read from single file, timestamped by the app. Both media type is aligned to different resolution grid, so the error can be up to half video frame time. acceptable, but not perfect. Can be currently fixed with exact user pacing, but can we make it easier? Can we automatically sync multiple streams of different types?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant