Revert "Merge pull request 'kernel + daemon: H.264 B-frame display reorder fix (closes #6)' (#7) from noether/kernel-daemon-h264-reorder-fix into main"

This reverts commit 79256dc7ef, reversing
changes made to 7ff2d897ea.
This commit is contained in:
2026-05-21 14:40:59 +02:00
parent 49e60c9bba
commit 714d781d22
6 changed files with 276 additions and 713 deletions
+37 -238
View File
@@ -133,288 +133,87 @@ static int send_response(struct chardev_client *cli, uint32_t type,
return rc;
}
/*
* Register a new (src_pts → cookie) mapping in the pending table.
* Reuses an existing slot for src_pts if one exists (defensive — the
* kernel should never re-use the same src_pts for two live cookies,
* but libva running against a test client without timestamps might
* send all-zero src_pts; collapse them onto the latest cookie so the
* 1:1-per-stream case keeps working). Returns 0 on success, -ENOSPC
* if the table is full.
*/
static int pending_register(struct chardev_client *cli, uint64_t src_pts,
uint32_t cookie,
const struct daedalus_req_decode *req)
{
int free_slot = -1;
int i;
for (i = 0; i < DAEDALUS_MAX_PENDING_COOKIES; i++) {
if (cli->pending[i].used && cli->pending[i].src_pts == src_pts) {
cli->pending[i].cookie = cookie;
cli->pending[i].cached_req = *req;
return 0;
}
if (!cli->pending[i].used && free_slot < 0)
free_slot = i;
}
if (free_slot < 0) {
log_err("pending: table full registering cookie=%u src_pts=%llu",
cookie, (unsigned long long) src_pts);
return -ENOSPC;
}
cli->pending[free_slot].used = 1;
cli->pending[free_slot].src_pts = src_pts;
cli->pending[free_slot].cookie = cookie;
cli->pending[free_slot].cached_req = *req;
return 0;
}
/*
* Look up the cookie + cached REQ_DECODE that originally introduced
* @src_pts. Returns 0 + populates @cookie_out / @req_out, or -ENOENT
* if no match (likely a daemon bug or codec output we can't route).
*/
static int pending_lookup(const struct chardev_client *cli,
uint64_t src_pts,
uint32_t *cookie_out,
struct daedalus_req_decode *req_out)
{
int i;
for (i = 0; i < DAEDALUS_MAX_PENDING_COOKIES; i++) {
if (cli->pending[i].used &&
cli->pending[i].src_pts == src_pts) {
*cookie_out = cli->pending[i].cookie;
*req_out = cli->pending[i].cached_req;
return 0;
}
}
return -ENOENT;
}
static void pending_release(struct chardev_client *cli, uint64_t src_pts)
{
int i;
for (i = 0; i < DAEDALUS_MAX_PENDING_COOKIES; i++) {
if (cli->pending[i].used &&
cli->pending[i].src_pts == src_pts) {
cli->pending[i].used = 0;
cli->pending[i].src_pts = 0;
cli->pending[i].cookie = 0;
return;
}
}
}
/*
* Pack the daemon's current AVFrame into the CAPTURE buffer owned by
* @owner_cookie, then ship RESP_FRAME with the flags caller asked for.
* Returns 0 on success; -errno on GET_DMABUF / mmap failure (RESP is
* still emitted so the kernel doesn't park the dst buffer forever).
*/
static int deliver_frame_to_cookie(struct chardev_client *cli,
uint32_t owner_cookie,
const struct daedalus_req_decode *owner_req,
struct daedalus_resp_frame *resp,
uint32_t resp_flags)
{
struct daedalus_capture_planes planes;
int orc;
orc = daedalus_capture_planes_open(cli->fd, owner_cookie, owner_req,
&planes);
if (orc < 0) {
log_warn("drain: GET_DMABUF cookie=%u failed (%d); RESP metadata-only",
owner_cookie, orc);
} else {
(void) daedalus_decoder_pack_current(cli->decoder, &planes,
owner_req->capture_pix_fmt);
daedalus_capture_planes_close(&planes);
}
resp->flags |= resp_flags;
return send_response(cli, DAEDALUS_MSG_RESP_FRAME, owner_cookie,
resp, sizeof(*resp));
}
static int handle_req_decode(struct chardev_client *cli,
const struct daedalus_msg_hdr *hdr,
const uint8_t *payload)
{
struct daedalus_req_decode req;
struct daedalus_resp_frame resp;
struct daedalus_capture_planes planes;
const struct daedalus_h264_meta *h264_meta = NULL;
size_t meta_off, meta_len = 0;
int submit_status;
int src_consumed_emitted = 0;
int rc;
int decoded = 0;
if (hdr->payload_len < sizeof(req)) {
struct daedalus_resp_frame err = { 0 };
log_err("REQ_DECODE cookie=%u: payload too short %u < %zu",
hdr->cookie, hdr->payload_len, sizeof(req));
err.status = DAEDALUS_DECODE_ERR_RECV;
err.flags = DAEDALUS_RESP_FLAG_HAS_PIXELS |
DAEDALUS_RESP_FLAG_SRC_CONSUMED;
memset(&resp, 0, sizeof(resp));
resp.status = DAEDALUS_DECODE_ERR_RECV;
return send_response(cli, DAEDALUS_MSG_RESP_FRAME,
hdr->cookie, &err, sizeof(err));
hdr->cookie, &resp, sizeof(resp));
}
memcpy(&req, payload, sizeof(req));
/* Optional H.264 meta block follows req when the flag is set;
* bitstream comes after meta. */
if (req.flags & DAEDALUS_REQ_FLAG_H264_META)
meta_len = sizeof(struct daedalus_h264_meta);
meta_off = sizeof(req);
if ((size_t) req.bitstream_len + sizeof(req) + meta_len !=
hdr->payload_len) {
struct daedalus_resp_frame err = { 0 };
log_err("REQ_DECODE cookie=%u: bitstream_len %u + meta %zu inconsistent with payload_len %u",
hdr->cookie, req.bitstream_len, meta_len,
hdr->payload_len);
err.status = DAEDALUS_DECODE_ERR_RECV;
err.flags = DAEDALUS_RESP_FLAG_HAS_PIXELS |
DAEDALUS_RESP_FLAG_SRC_CONSUMED;
memset(&resp, 0, sizeof(resp));
resp.status = DAEDALUS_DECODE_ERR_RECV;
return send_response(cli, DAEDALUS_MSG_RESP_FRAME,
hdr->cookie, &err, sizeof(err));
hdr->cookie, &resp, sizeof(resp));
}
if (meta_len)
h264_meta = (const struct daedalus_h264_meta *)
(payload + meta_off);
log_info("REQ_DECODE cookie=%u codec=%u bitstream=%u bytes meta=%s capture=%ux%u %u planes src_pts=%llu",
log_info("REQ_DECODE cookie=%u codec=%u bitstream=%u bytes meta=%s capture=%ux%u %u planes",
hdr->cookie, req.codec_id, req.bitstream_len,
h264_meta ? "h264" : "none",
req.capture_width, req.capture_height,
req.capture_num_planes,
(unsigned long long) req.src_pts);
req.capture_num_planes);
/*
* Register (src_pts → cookie) mapping BEFORE submit, so any drained
* frame whose pts matches this REQ's src_pts (the steady-state
* 1:1 path) can find its owner via pending_lookup below. Out of
* space here is fatal — we'd lose the routing identity for this
* cookie's eventual frame. Send an error RESP that releases both
* src and dst so the V4L2 client moves on.
* Open dmabuf-fds for every CAPTURE plane and mmap them.
* If this fails we still attempt the decode (so the kernel
* gets a structured error response) — but we pass NULL
* planes so pixels aren't written anywhere.
*/
rc = pending_register(cli, req.src_pts, hdr->cookie, &req);
rc = daedalus_capture_planes_open(cli->fd, hdr->cookie, &req,
&planes);
if (rc < 0) {
struct daedalus_resp_frame err = { 0 };
err.status = DAEDALUS_DECODE_ERR_SEND;
err.flags = DAEDALUS_RESP_FLAG_HAS_PIXELS |
DAEDALUS_RESP_FLAG_SRC_CONSUMED;
return send_response(cli, DAEDALUS_MSG_RESP_FRAME,
hdr->cookie, &err, sizeof(err));
log_warn("REQ_DECODE cookie=%u: GET_DMABUF/mmap failed (%d); decode metadata-only",
hdr->cookie, rc);
/* planes is already zeroed by capture_planes_open */
}
submit_status = daedalus_decoder_submit(cli->decoder, &req,
payload + meta_off + meta_len,
h264_meta);
if (submit_status != 0) {
/*
* avcodec_send_packet failed before any frame could have
* been queued for this src_pts. Drop the pending entry
* (no future drain will find a matching pts), and emit a
* combined HAS_PIXELS|SRC_CONSUMED error RESP for this
* cookie so the V4L2 client unblocks.
*/
struct daedalus_resp_frame err = { 0 };
rc = daedalus_decoder_run_request(cli->decoder, &req,
payload + meta_off + meta_len,
h264_meta,
&resp,
planes.nr ? &planes : NULL);
decoded = (rc >= 0);
pending_release(cli, req.src_pts);
err.status = (uint32_t) submit_status;
err.codec_id = req.codec_id;
err.flags = DAEDALUS_RESP_FLAG_HAS_PIXELS |
DAEDALUS_RESP_FLAG_SRC_CONSUMED;
err.output_src_pts = req.src_pts;
return send_response(cli, DAEDALUS_MSG_RESP_FRAME,
hdr->cookie, &err, sizeof(err));
}
daedalus_capture_planes_close(&planes);
if (!decoded)
return rc;
/*
* Drain libavcodec for as many display-ordered frames as it can
* emit right now. Each frame's pts identifies which cookie's
* CAPTURE buffer the pixels go in (see [[daedalus-v4l2#6]]). In
* steady state for VP9/AV1 (no reorder) the loop runs exactly
* once, draining the just-submitted packet's own frame. For
* H.264 with B-frames the first drained frame may belong to an
* EARLIER cookie's bitstream — that's the entire point.
* RESP_FRAME is metadata-only in Phase 8.6 — pixels already
* live in the V4L2 client's CAPTURE buffer via the dmabuf
* the daemon wrote to in pack_nv12_to_planes.
*/
for (;;) {
struct daedalus_resp_frame resp;
uint32_t owner_cookie = 0;
struct daedalus_req_decode owner_req;
uint32_t flags;
rc = daedalus_decoder_drain_one(cli->decoder, req.codec_id,
&resp);
if (rc == -EAGAIN)
break;
if (rc != 0) {
/*
* Hard codec error during drain. resp->status is set.
* Pin it to THIS REQ's cookie (we can't know whose
* pts the failed frame would have had); set both
* flags so the V4L2 client moves on.
*/
pending_release(cli, req.src_pts);
resp.flags = DAEDALUS_RESP_FLAG_HAS_PIXELS |
DAEDALUS_RESP_FLAG_SRC_CONSUMED;
resp.output_src_pts = req.src_pts;
(void) send_response(cli, DAEDALUS_MSG_RESP_FRAME,
hdr->cookie, &resp, sizeof(resp));
src_consumed_emitted = 1;
break;
}
if (pending_lookup(cli, resp.output_src_pts,
&owner_cookie, &owner_req) != 0) {
/*
* Frame's pts has no registered owner — implies a
* daemon-side tracking bug or a codec output for a
* packet we never registered (e.g. a B-frame that
* was queued before the daemon caught up). Drop the
* frame; can't safely route it.
*/
log_warn("drain: no pending entry for output_src_pts=%llu (codec dropped a frame?)",
(unsigned long long) resp.output_src_pts);
continue;
}
flags = DAEDALUS_RESP_FLAG_HAS_PIXELS;
if (owner_cookie == hdr->cookie) {
flags |= DAEDALUS_RESP_FLAG_SRC_CONSUMED;
src_consumed_emitted = 1;
}
(void) deliver_frame_to_cookie(cli, owner_cookie, &owner_req,
&resp, flags);
pending_release(cli, resp.output_src_pts);
}
/*
* If the drain loop didn't already SRC_CONSUMED this REQ's cookie
* (libavcodec held the frame for display-order reorder — the
* pixels will arrive in a future drain), emit a standalone
* SRC_CONSUMED RESP now. Kernel releases src_buf + runs
* job_finish; dst_buf parked until the matching HAS_PIXELS
* shows up later.
*/
if (!src_consumed_emitted) {
struct daedalus_resp_frame resp = { 0 };
resp.status = DAEDALUS_DECODE_OK;
resp.codec_id = req.codec_id;
resp.flags = DAEDALUS_RESP_FLAG_SRC_CONSUMED;
(void) send_response(cli, DAEDALUS_MSG_RESP_FRAME,
hdr->cookie, &resp, sizeof(resp));
}
return 0;
return send_response(cli, DAEDALUS_MSG_RESP_FRAME, hdr->cookie,
&resp, sizeof(resp));
}
static int handle_ping(struct chardev_client *cli,
-26
View File
@@ -18,44 +18,18 @@
struct ffmpeg_loader;
struct daedalus_decoder;
/*
* Per-inflight (cookie, src_pts) tracking for the H.264 B-frame
* display-reorder fix (daedalus-v4l2#6). When the daemon drains a
* frame from libavcodec, frame->pts (= src_pts of the OUTPUT bitstream
* that contained the frame's slices) identifies which cookie's CAPTURE
* buffer the pixels belong in — distinct from the cookie of the REQ
* that triggered the receive_frame call. Mapping is small (bounded
* by the V4L2 client's buffer pool depth, typically ≤24) so a linear
* array beats a hashtable for cache-locality.
*
* cached_req carries the capture geometry (num_planes, plane sizes,
* strides, pix_fmt) so a later drain — which may target this cookie
* from a DIFFERENT REQ's drain loop — can call GET_DMABUF + open
* planes with the original REQ's parameters.
*/
#define DAEDALUS_MAX_PENDING_COOKIES 64
struct chardev_pending_cookie {
int used;
uint64_t src_pts;
uint32_t cookie;
struct daedalus_req_decode cached_req;
};
/**
* struct chardev_client - daemon-side chardev state
* @fd: open /dev/daedalus-v4l2 descriptor (-1 if not open)
* @loader: dlopen'd FFmpeg loader (borrowed; not owned)
* @decoder: per-codec AVCodecContext cache (owned)
* @stop_flag: set non-zero from a signal handler to break the loop
* @pending: pts → cookie lookup table for split SRC/DST RESPs
*/
struct chardev_client {
int fd;
struct ffmpeg_loader *loader;
struct daedalus_decoder *decoder;
volatile sig_atomic_t *stop_flag;
struct chardev_pending_cookie pending[DAEDALUS_MAX_PENDING_COOKIES];
};
/**
+126 -154
View File
@@ -348,30 +348,31 @@ static int pack_nv12_to_planes(struct AVFrame *fr,
return 0;
}
/*
* Per-codec assemble + send_packet. Returns 0 on success, or one
* of DAEDALUS_DECODE_ERR_* on failure (errors here propagate via
* the caller's RESP_FRAME status field — they are NOT logged as a
* silent skip). pkt->pts is stamped from req->src_pts so the
* resulting frame->pts comes back identifiable on the drain side.
*/
int daedalus_decoder_submit(struct daedalus_decoder *dec,
const struct daedalus_req_decode *req,
const uint8_t *bitstream,
const struct daedalus_h264_meta *h264_meta)
int daedalus_decoder_run_request(struct daedalus_decoder *dec,
const struct daedalus_req_decode *req,
const uint8_t *bitstream,
const struct daedalus_h264_meta *h264_meta,
struct daedalus_resp_frame *resp,
const struct daedalus_capture_planes *planes)
{
struct ffmpeg_loader *fm = dec->loader;
struct AVCodecContext *ctx = NULL;
uint8_t *assembled = NULL;
size_t assembled_len = 0;
int rc;
int status = 0;
memset(resp, 0, sizeof(*resp));
resp->codec_id = req->codec_id;
rc = decoder_open_codec(dec, req->codec_id, &ctx);
if (rc == -ENOSYS)
return DAEDALUS_DECODE_ERR_CODEC;
if (rc < 0)
return DAEDALUS_DECODE_ERR_OPEN;
if (rc == -ENOSYS) {
resp->status = DAEDALUS_DECODE_ERR_CODEC;
goto out;
}
if (rc < 0) {
resp->status = DAEDALUS_DECODE_ERR_OPEN;
goto out;
}
fm->av_packet_unref(dec->pkt);
@@ -396,14 +397,14 @@ int daedalus_decoder_submit(struct daedalus_decoder *dec,
if (sps_len == 0 || pps_len == 0) {
log_err("decoder: SPS/PPS NAL synth failed (sps=%zu pps=%zu)",
sps_len, pps_len);
status = DAEDALUS_DECODE_ERR_SEND;
resp->status = DAEDALUS_DECODE_ERR_SEND;
goto out;
}
assembled_len = sps_len + pps_len + req->bitstream_len;
assembled = malloc(assembled_len + AV_INPUT_BUFFER_PADDING_SIZE);
if (!assembled) {
status = DAEDALUS_DECODE_ERR_SEND;
resp->status = DAEDALUS_DECODE_ERR_SEND;
goto out;
}
memcpy(assembled, sps_nal, sps_len);
@@ -440,162 +441,133 @@ int daedalus_decoder_submit(struct daedalus_decoder *dec,
dec->pkt->size = (int) req->bitstream_len;
}
/*
* Stamp pkt->pts from REQ_DECODE's src_pts (the V4L2 OUTPUT
* buffer's vb2 timestamp captured by the kernel at device_run
* time). libavcodec carries pkt->pts forward to frame->pts on
* the receive_frame side — even after display-order reordering
* inside the H.264 DPB — which lets the chardev_client identify
* which cookie's CAPTURE buffer a drained frame's pixels belong
* in. Without this stamp, every drained frame would look like
* it came from the current REQ; pairs of B/P would swap places
* in the visible output (daedalus-v4l2#6).
*/
dec->pkt->pts = (int64_t) req->src_pts;
rc = fm->avcodec_send_packet(ctx, dec->pkt);
if (rc < 0) {
log_err("decoder: avcodec_send_packet failed: %d", rc);
status = DAEDALUS_DECODE_ERR_SEND;
resp->status = DAEDALUS_DECODE_ERR_SEND;
goto out;
}
out:
free(assembled);
(void) assembled_len;
return status;
}
/*
* Pull the next display-ordered frame out of libavcodec's DPB.
* Returns 0 if a frame was returned (dec->frame holds it and resp
* is populated with metadata + output_src_pts == frame->pts),
* -EAGAIN if libavcodec needs more input, or DAEDALUS_DECODE_ERR_*
* on a hard codec error. Caller may immediately invoke
* daedalus_decoder_pack_current() to copy this frame's pixels into
* a CAPTURE buffer's mapped planes, then call drain_one again for
* any further frames in the DPB.
*/
int daedalus_decoder_drain_one(struct daedalus_decoder *dec,
uint32_t codec_id,
struct daedalus_resp_frame *resp)
{
struct ffmpeg_loader *fm = dec->loader;
struct AVCodecContext *ctx = NULL;
struct AVFrame *fr;
const AVPixFmtDescriptor *desc;
uint32_t h, luma_len = 0, chroma_len = 0;
int rc;
memset(resp, 0, sizeof(*resp));
resp->codec_id = codec_id;
rc = decoder_open_codec(dec, codec_id, &ctx);
if (rc == -ENOSYS) {
resp->status = DAEDALUS_DECODE_ERR_CODEC;
return DAEDALUS_DECODE_ERR_CODEC;
}
if (rc < 0) {
resp->status = DAEDALUS_DECODE_ERR_OPEN;
return DAEDALUS_DECODE_ERR_OPEN;
}
fm->av_frame_unref(dec->frame);
rc = fm->avcodec_receive_frame(ctx, dec->frame);
if (rc == AVERROR(EAGAIN) || rc == AVERROR_EOF)
return -EAGAIN;
if (rc == AVERROR(EAGAIN) || rc == AVERROR_EOF) {
log_debug("decoder: no frame ready yet (rc=%d)", rc);
resp->status = DAEDALUS_DECODE_NO_FRAME;
goto out;
}
if (rc < 0) {
log_err("decoder: avcodec_receive_frame failed: %d", rc);
resp->status = DAEDALUS_DECODE_ERR_RECV;
return DAEDALUS_DECODE_ERR_RECV;
goto out;
}
fr = dec->frame;
desc = fm->av_pix_fmt_desc_get(fr->format);
h = fnv1a32_init();
{
struct AVFrame *fr = dec->frame;
const AVPixFmtDescriptor *desc =
fm->av_pix_fmt_desc_get(fr->format);
uint32_t h = fnv1a32_init();
uint32_t luma_len = 0, chroma_len = 0;
resp->status = DAEDALUS_DECODE_OK;
resp->width = (uint32_t) fr->width;
resp->height = (uint32_t) fr->height;
resp->pix_fmt = fr->format;
resp->output_src_pts = (uint64_t) fr->pts;
resp->status = DAEDALUS_DECODE_OK;
resp->width = (uint32_t) fr->width;
resp->height = (uint32_t) fr->height;
resp->pix_fmt = fr->format;
if (!desc) {
log_warn("decoder: no descriptor for pix_fmt %d", fr->format);
} else {
int p, max_plane = 0;
int i;
/*
* Walk every plane reported by the AVPixFmtDescriptor.
* For each component, byte width = ((plane_w *
* step_minus1) >> 0) — but the descriptor only tells
* us which plane each component sits in, not the
* plane's byte stride per pixel. In practice for the
* formats we care about (YUV420P, YUV422P, YUV444P,
* GBRP, NV12), each plane has exactly one component
* at 1 byte/sample. Hash each plane at
* (width >> log2_chroma_w) × (height >> log2_chroma_h)
* for chroma planes, full-size for plane 0.
*
* This generalises cleanly to anything 8-bit-per-
* sample-per-plane; 10/12-bit (P010, YUV420P10LE) will
* need depth handling when Phase 8.6 brings HDR
* content into play.
*/
if (!desc) {
log_warn("decoder: no descriptor for pix_fmt %d",
fr->format);
} else {
int p, max_plane = 0;
int i;
for (i = 0; i < desc->nb_components; i++) {
if (desc->comp[i].plane > max_plane)
max_plane = desc->comp[i].plane;
}
for (p = 0; p <= max_plane; p++) {
int pw, ph;
if (!fr->data[p] || !fr->linesize[p])
continue;
if (p == 0) {
pw = fr->width;
ph = fr->height;
luma_len += (uint32_t) pw * (uint32_t) ph;
} else {
pw = AV_CEIL_RSHIFT(fr->width,
desc->log2_chroma_w);
ph = AV_CEIL_RSHIFT(fr->height,
desc->log2_chroma_h);
chroma_len += (uint32_t) pw * (uint32_t) ph;
for (i = 0; i < desc->nb_components; i++) {
if (desc->comp[i].plane > max_plane)
max_plane = desc->comp[i].plane;
}
for (p = 0; p <= max_plane; p++) {
int pw, ph;
if (!fr->data[p] || !fr->linesize[p])
continue;
if (p == 0) {
pw = fr->width;
ph = fr->height;
luma_len += (uint32_t) pw *
(uint32_t) ph;
} else {
pw = AV_CEIL_RSHIFT(fr->width,
desc->log2_chroma_w);
ph = AV_CEIL_RSHIFT(fr->height,
desc->log2_chroma_h);
chroma_len += (uint32_t) pw *
(uint32_t) ph;
}
h = fnv1a32_plane(h, fr->data[p], pw, ph,
fr->linesize[p]);
}
h = fnv1a32_plane(h, fr->data[p], pw, ph,
fr->linesize[p]);
}
resp->luma_len = luma_len;
resp->chroma_len = chroma_len;
resp->fnv1a_yuv = h;
/*
* Pack pixels directly into the mapped CAPTURE dmabuf
* planes. Dispatch on the V4L2 fourcc the kernel
* negotiated:
* V4L2_PIX_FMT_NV12M (default, 8-bit, 2 planes)
* V4L2_PIX_FMT_P010 (10-bit HDR, 1 plane)
*/
if (planes && planes->nr >= 1) {
int prc = 0;
switch (req->capture_pix_fmt) {
case V4L2_PIX_FMT_NV12M:
prc = pack_nv12_to_planes(fr, desc, planes);
break;
case V4L2_PIX_FMT_NV12:
prc = pack_nv12_single_to_plane(fr, desc, planes);
break;
case V4L2_PIX_FMT_P010:
prc = pack_p010_to_plane(fr, desc, planes);
break;
default:
log_warn("decoder: unsupported capture fourcc 0x%08x",
req->capture_pix_fmt);
prc = -EINVAL;
break;
}
if (prc < 0)
log_warn("decoder: pack failed (pix_fmt=%d cap_fourcc=0x%08x) — kernel will see metadata only",
fr->format, req->capture_pix_fmt);
}
log_info("decoder: OK %dx%d fmt=%d (%s) fnv1a=0x%08x luma=%u chroma=%u",
fr->width, fr->height, fr->format,
desc ? desc->name : "?",
h, luma_len, chroma_len);
}
resp->luma_len = luma_len;
resp->chroma_len = chroma_len;
resp->fnv1a_yuv = h;
log_info("decoder: OK %dx%d fmt=%d (%s) fnv1a=0x%08x luma=%u chroma=%u src_pts=%llu",
fr->width, fr->height, fr->format,
desc ? desc->name : "?",
h, luma_len, chroma_len,
(unsigned long long) fr->pts);
fm->av_frame_unref(dec->frame);
out:
free(assembled);
(void) assembled_len;
return 0;
}
int daedalus_decoder_pack_current(struct daedalus_decoder *dec,
const struct daedalus_capture_planes *planes,
uint32_t capture_pix_fmt)
{
struct ffmpeg_loader *fm = dec->loader;
struct AVFrame *fr = dec->frame;
const AVPixFmtDescriptor *desc;
int prc;
if (!planes || planes->nr < 1 || !fr || !fr->width || !fr->height)
return -EINVAL;
desc = fm->av_pix_fmt_desc_get(fr->format);
switch (capture_pix_fmt) {
case V4L2_PIX_FMT_NV12M:
prc = pack_nv12_to_planes(fr, desc, planes);
break;
case V4L2_PIX_FMT_NV12:
prc = pack_nv12_single_to_plane(fr, desc, planes);
break;
case V4L2_PIX_FMT_P010:
prc = pack_p010_to_plane(fr, desc, planes);
break;
default:
log_warn("decoder: unsupported capture fourcc 0x%08x",
capture_pix_fmt);
prc = -EINVAL;
break;
}
if (prc < 0)
log_warn("decoder: pack failed (pix_fmt=%d cap_fourcc=0x%08x)",
fr->format, capture_pix_fmt);
return prc;
}
+22 -57
View File
@@ -56,68 +56,33 @@ int daedalus_decoder_init(struct daedalus_decoder *dec,
void daedalus_decoder_cleanup(struct daedalus_decoder *dec);
/**
* daedalus_decoder_submit - send one REQ_DECODE's bitstream into libavcodec
* daedalus_decoder_run_request - decode one REQ_DECODE payload
* @dec: initialised decoder
* @req: REQ_DECODE prefix (from the wire); src_pts is stamped on
* the AVPacket so libavcodec returns frame->pts == src_pts
* when it eventually outputs the matching frame in display
* order (daedalus-v4l2#6).
* @req: REQ_DECODE prefix (from the wire)
* @bitstream: bitstream blob (req->bitstream_len bytes)
* @h264_meta: optional H.264 SPS/PPS metadata; non-NULL only when
* codec_id == H264 and the kernel set DAEDALUS_REQ_FLAG_
* H264_META. See decoder.c for the AnnexB synthesis.
* H264_META. Used to synthesise the AnnexB SPS+PPS NALs
* libavcodec needs before any slice (libva-v4l2-request
* passes only the slice in @bitstream per the V4L2
* stateless API contract). NULL for VP9/AV1 paths.
* @resp: caller-allocated RESP_FRAME output (zeroed by callee)
* @planes: mapped CAPTURE planes (Phase 8.6 dmabuf path). If
* NULL or planes->nr == 0, the decoder runs but
* writes no pixels — caller still gets dims + digest.
*
* Calls avcodec_send_packet on the codec's per-codec AVCodecContext.
* Returns 0 on success; one of DAEDALUS_DECODE_ERR_* on failure
* (which the caller should propagate as the RESP_FRAME status for
* the cookie of this REQ). Does NOT call avcodec_receive_frame —
* use daedalus_decoder_drain_one for that.
* Populates @resp with the decode outcome and writes decoded
* pixels (NV12 layout: Y to plane 0, interleaved CbCr to plane
* 1) directly into the mapped dmabuf planes. Always returns
* 0; decode-level failures are reported via @resp->status so
* the kernel sees a structured response rather than a dropped
* request.
*/
int daedalus_decoder_submit(struct daedalus_decoder *dec,
const struct daedalus_req_decode *req,
const uint8_t *bitstream,
const struct daedalus_h264_meta *h264_meta);
/**
* daedalus_decoder_drain_one - pop the next display-ordered frame, if any
* @dec: initialised decoder
* @codec_id: which codec context to drain (matches the REQ that just
* called submit). VP9/AV1/H264 use independent contexts.
* @resp: caller-allocated RESP_FRAME output (zeroed by callee).
* On a successful drain (return 0), resp's status / width /
* height / pix_fmt / luma_len / chroma_len / fnv1a_yuv /
* output_src_pts are populated; flags is left at 0 (caller
* adds HAS_PIXELS / SRC_CONSUMED). On EAGAIN, resp is
* zeroed.
*
* Return: 0 on a frame returned, -EAGAIN if libavcodec needs more
* input (display-order frame held inside DPB), <0 on a hard codec
* error (resp->status set).
*
* After a successful drain, the dec's internal AVFrame holds the
* decoded picture. Caller may immediately call
* daedalus_decoder_pack_current(planes) to write that picture into
* a CAPTURE buffer's dmabuf-mapped planes. Subsequent calls to
* drain_one (without another submit) try to pull additional frames
* from libavcodec's DPB.
*/
int daedalus_decoder_drain_one(struct daedalus_decoder *dec,
uint32_t codec_id,
struct daedalus_resp_frame *resp);
/**
* daedalus_decoder_pack_current - pack the last drained frame into planes
* @dec: initialised decoder; must have a frame from drain_one
* @planes: mapped CAPTURE planes (open via GET_DMABUF using the
* cookie that owns the frame's output_src_pts).
* @capture_pix_fmt: V4L2 fourcc on the CAPTURE side (NV12M, NV12,
* P010).
*
* Return: 0 on success, <0 on a pack failure (kernel sees only the
* metadata, not pixels — typical when a format isn't wired yet).
*/
int daedalus_decoder_pack_current(struct daedalus_decoder *dec,
const struct daedalus_capture_planes *planes,
uint32_t capture_pix_fmt);
int daedalus_decoder_run_request(struct daedalus_decoder *dec,
const struct daedalus_req_decode *req,
const uint8_t *bitstream,
const struct daedalus_h264_meta *h264_meta,
struct daedalus_resp_frame *resp,
const struct daedalus_capture_planes *planes);
#endif /* DAEDALUS_V4L2_DECODER_H */