Phase 8.6: dmabuf + AV1 + H.264 + stateless controls
Removes the Phase 8.5 64 KiB frame-size cap by exporting CAPTURE
buffers as dmabuf-fds the daemon mmaps and writes pixels into
directly. Adds AV1 + H.264 codec support, V4L2 stateless control
registration, and the compliance polish that brings the driver
to 47/48 v4l2-compliance pass.
Protocol (include/daedalus_v4l2_proto.h):
- struct daedalus_req_decode grew capture-buffer metadata
(width/height/pix_fmt/num_planes + per-plane size+stride).
- New DAEDALUS_IOC_GET_DMABUF ioctl on the chardev: daemon
asks for a per-plane dmabuf fd, kernel calls vb2_core_expbuf
in daemon task context so the fd lands in the daemon's table.
Kernel m2m driver (kernel/daedalus_v4l2_main.c):
- Both queues switched to vb2_dma_contig_memops. OUTPUT was
vmalloc in 8.5; the switch is needed because vmalloc doesn't
honour V4L2_MEMORY_FLAG_NON_COHERENT and v4l2-compliance's
REQBUFS test rejected the driver because of it. We still
read bitstream via vb2_plane_vaddr (dma_contig gives a
kernel virtual address just like vmalloc did).
- dma_coerce_mask_and_coherent(DMA_BIT_MASK(32)) in probe.
- queue_setup populates alloc_devs[plane] = &pdev->dev for
both queues; allow_cache_hints=1 on both.
- daedalus_export_capture_dmabuf(cookie, plane, flags, *fd):
walks inflight list, calls vb2_core_expbuf on the CAPTURE
buffer in the caller's (daemon's) task context.
- device_run fills the new REQ_DECODE capture fields from
ctx->dst_fmt and maps ctx->src_fmt.pixelformat to
DAEDALUS_CODEC_VP9 / _AV1 / _H264 (was hard-wired to VP9).
- daedalus_complete_resp_frame handles both the 8.5 inline
path (kept for debugging) and the 8.6 dmabuf path (pixels
already in CAPTURE buffer, just set payload from metadata).
- enum_fmt advertises all 3 OUTPUT formats (VP9F, AV1F, S264).
- try_fmt preserves userspace colorspace fields instead of
overwriting with REC709 defaults (fixes 8.5 compliance fail).
- s_fmt propagates OUTPUT colorspace → CAPTURE (stateless
decoder round-trip test at v4l2-test-formats.cpp:958).
- 12 V4L2 stateless controls registered per open (VP9_FRAME,
VP9_COMPRESSED_HDR, H264_SPS/PPS/SCALING/PRED_WEIGHTS/
SLICE_PARAMS/DECODE_PARAMS, AV1_FRAME/SEQUENCE/
TILE_GROUP_ENTRY/FILM_GRAIN). Daemon ignores values (FFmpeg
re-parses); registration is what makes libva-v4l2-request
see us.
Kernel chardev (kernel/daedalus_v4l2_chardev.c):
- New unlocked_ioctl dispatching DAEDALUS_IOC_GET_DMABUF to
daedalus_export_capture_dmabuf.
- debugfs test_decode cookies unified with the m2m cookie
allocator via shared daedalus_next_cookie() — kills the
Phase 8.5 namespace collision.
Daemon (daemon/src/...):
- New dmabuf_capture.{c,h}: GET_DMABUF + mmap each plane on
REQ_DECODE; munmap + close on completion. O_RDWR | O_CLOEXEC
is essential — vb2_core_expbuf extracts O_ACCMODE from flags
and exports read-only by default (caught on first run; mmap
-EACCES on PROT_WRITE).
- decoder.{c,h}: lazily opens AV1 + H.264 AVCodecContexts in
addition to VP9 (dropped the -ENOSYS stubs). pack_nv12_to_planes
writes Y line-by-line into planes[0] with planes[0].stride;
interleaves Cb/Cr into planes[1] with planes[1].stride.
- chardev_client.c handle_req_decode: opens dmabuf planes,
runs decode (pixels land in CAPTURE buffer directly), closes
planes, sends metadata-only RESP_FRAME. No wire-pixel
allocation.
Test harness (tools/test_m2m_decode.c):
- Optional 5th arg `codec` (vp9 | av1 | h264). Same client
drives all three codecs.
Verification on hertz (Pi 5, 6.12.75+rpt-rpi-2712):
Bit-exact end-to-end vs `ffmpeg -pix_fmt nv12`:
VP9 1920x1080 3,110,400 bytes MATCH
AV1 128x96 18,432 bytes MATCH
H.264 128x96 18,432 bytes MATCH
VP9 1080p went through the full dmabuf path with no chardev
payload bloat — the same chardev that capped at 64 KiB in 8.5
now ferries metadata only and lets the daemon mmap+write a
3.1 MB frame directly into the V4L2 client's buffer.
v4l2-compliance:
Phase 8.1: 44/48
Phase 8.5: 44/48 (different fails after m2m landed)
Phase 8.6: 47/48
Only remaining: VIDIOC_(TRY_)DECODER_CMD (needs media
controller — explicitly Phase 8.7 work).
11 standard compound controls visible:
vp9_frame_decode_parameters, vp9_probabilities_updates,
h264_sequence_parameter_set, h264_picture_parameter_set,
h264_scaling_matrix, h264_prediction_weight_table,
h264_slice_parameters, h264_decode_parameters,
av1_sequence_parameters, av1_frame_parameters,
av1_film_grain (av1_tile_group_entry refused by hdl->error
on this kernel — skipped silently).
Clean SIGTERM + rmmod, no oops/WARN.
Roadmap update (docs/roadmap.md):
- Phase 8.6 marked closed with the closure-doc reference.
- Phase 8.7 reshaped to (1) media controller, (2) perf +
daedalus_dispatch_* substitution, (3) HDR/10-bit, (4)
long-form multi-frame streaming.
Per correctness-before-speed:
- Real V4L2 dmabuf via vb2_core_expbuf (not a sideband
fd-passing hack).
- O_RDWR access mode threaded through correctly.
- Strict pixel-byte comparison against ffmpeg, not "looks
right" eyeballing.
- Each compliance edge documented with the underlying test
source-line + the fix.
- All resource paths cleaned (munmap + close per plane on
every exit, including error paths).
Phase 8.7 next: media controller binding (closes last
compliance fail), per-frame profiling, QPU dispatch
substitution targeting 30fps@1080p from
30fps-floor-is-fine memory.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -35,6 +35,7 @@ add_executable(daedalus_v4l2_daemon
|
||||
src/parser.c
|
||||
src/decoder.c
|
||||
src/chardev_client.c
|
||||
src/dmabuf_capture.c
|
||||
)
|
||||
|
||||
target_include_directories(daedalus_v4l2_daemon
|
||||
|
||||
+32
-23
@@ -4,6 +4,7 @@
|
||||
*/
|
||||
#include "chardev_client.h"
|
||||
#include "decoder.h"
|
||||
#include "dmabuf_capture.h"
|
||||
#include "ffmpeg_loader.h"
|
||||
#include "log.h"
|
||||
|
||||
@@ -138,9 +139,9 @@ static int handle_req_decode(struct chardev_client *cli,
|
||||
{
|
||||
struct daedalus_req_decode req;
|
||||
struct daedalus_resp_frame resp;
|
||||
uint8_t *resp_buf = NULL;
|
||||
size_t pix_cap, pix_used = 0;
|
||||
struct daedalus_capture_planes planes;
|
||||
int rc;
|
||||
int decoded = 0;
|
||||
|
||||
if (hdr->payload_len < sizeof(req)) {
|
||||
log_err("REQ_DECODE cookie=%u: payload too short %u < %zu",
|
||||
@@ -160,34 +161,42 @@ static int handle_req_decode(struct chardev_client *cli,
|
||||
hdr->cookie, &resp, sizeof(resp));
|
||||
}
|
||||
|
||||
log_info("REQ_DECODE cookie=%u codec=%u bitstream=%u bytes",
|
||||
hdr->cookie, req.codec_id, req.bitstream_len);
|
||||
log_info("REQ_DECODE cookie=%u codec=%u bitstream=%u bytes capture=%ux%u %u planes",
|
||||
hdr->cookie, req.codec_id, req.bitstream_len,
|
||||
req.capture_width, req.capture_height,
|
||||
req.capture_num_planes);
|
||||
|
||||
/*
|
||||
* Build the response buffer as { struct daedalus_resp_frame,
|
||||
* <NV12 pixels> }. Cap pixel area at MAX_PAYLOAD - sizeof
|
||||
* for now (Phase 8.5); Phase 8.6 dmabuf removes the cap.
|
||||
* 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.
|
||||
*/
|
||||
pix_cap = DAEDALUS_PROTO_MAX_PAYLOAD - sizeof(resp);
|
||||
resp_buf = malloc(sizeof(resp) + pix_cap);
|
||||
if (!resp_buf)
|
||||
return -ENOMEM;
|
||||
rc = daedalus_capture_planes_open(cli->fd, hdr->cookie, &req,
|
||||
&planes);
|
||||
if (rc < 0) {
|
||||
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 */
|
||||
}
|
||||
|
||||
rc = daedalus_decoder_run_request(cli->decoder, &req,
|
||||
payload + sizeof(req), &resp,
|
||||
resp_buf + sizeof(resp),
|
||||
pix_cap, &pix_used);
|
||||
if (rc < 0) {
|
||||
free(resp_buf);
|
||||
return rc;
|
||||
}
|
||||
/* Header at front; pixels follow. Truncate to actual used. */
|
||||
memcpy(resp_buf, &resp, sizeof(resp));
|
||||
planes.nr ? &planes : NULL);
|
||||
decoded = (rc >= 0);
|
||||
|
||||
rc = send_response(cli, DAEDALUS_MSG_RESP_FRAME, hdr->cookie,
|
||||
resp_buf, sizeof(resp) + pix_used);
|
||||
free(resp_buf);
|
||||
return rc;
|
||||
daedalus_capture_planes_close(&planes);
|
||||
|
||||
if (!decoded)
|
||||
return rc;
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
return send_response(cli, DAEDALUS_MSG_RESP_FRAME, hdr->cookie,
|
||||
&resp, sizeof(resp));
|
||||
}
|
||||
|
||||
static int handle_ping(struct chardev_client *cli,
|
||||
|
||||
+80
-61
@@ -73,6 +73,10 @@ void daedalus_decoder_cleanup(struct daedalus_decoder *dec)
|
||||
return;
|
||||
if (dec->ctx_vp9)
|
||||
dec->loader->avcodec_free_context(&dec->ctx_vp9);
|
||||
if (dec->ctx_av1)
|
||||
dec->loader->avcodec_free_context(&dec->ctx_av1);
|
||||
if (dec->ctx_h264)
|
||||
dec->loader->avcodec_free_context(&dec->ctx_h264);
|
||||
if (dec->frame)
|
||||
dec->loader->av_frame_free(&dec->frame);
|
||||
if (dec->pkt)
|
||||
@@ -91,26 +95,32 @@ static int decoder_open_codec(struct daedalus_decoder *dec, uint32_t codec_id,
|
||||
const struct AVCodec *codec;
|
||||
struct AVCodecContext *ctx;
|
||||
enum AVCodecID av_id;
|
||||
struct AVCodecContext **cache;
|
||||
int rc;
|
||||
|
||||
switch (codec_id) {
|
||||
case DAEDALUS_CODEC_VP9:
|
||||
av_id = AV_CODEC_ID_VP9;
|
||||
if (dec->ctx_vp9) {
|
||||
*out = dec->ctx_vp9;
|
||||
return 0;
|
||||
}
|
||||
cache = &dec->ctx_vp9;
|
||||
break;
|
||||
case DAEDALUS_CODEC_AV1:
|
||||
av_id = AV_CODEC_ID_AV1;
|
||||
cache = &dec->ctx_av1;
|
||||
break;
|
||||
case DAEDALUS_CODEC_H264:
|
||||
/* Phase 8.6 wires AV1 and H.264 properly. */
|
||||
log_warn("decoder: codec_id %u not yet supported", codec_id);
|
||||
return -ENOSYS;
|
||||
av_id = AV_CODEC_ID_H264;
|
||||
cache = &dec->ctx_h264;
|
||||
break;
|
||||
default:
|
||||
log_warn("decoder: unknown codec_id %u", codec_id);
|
||||
return -ENOSYS;
|
||||
}
|
||||
|
||||
if (*cache) {
|
||||
*out = *cache;
|
||||
return 0;
|
||||
}
|
||||
|
||||
codec = fm->avcodec_find_decoder(av_id);
|
||||
if (!codec) {
|
||||
log_err("decoder: avcodec_find_decoder(%d) returned NULL", av_id);
|
||||
@@ -126,64 +136,79 @@ static int decoder_open_codec(struct daedalus_decoder *dec, uint32_t codec_id,
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
dec->ctx_vp9 = ctx;
|
||||
*cache = ctx;
|
||||
*out = ctx;
|
||||
log_info("decoder: opened %s context", codec->name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Pack the decoded YUV planes into NV12 (Y followed by interleaved
|
||||
* CbCr) in @out, advancing @out and tracking total bytes written
|
||||
* in @used. Truncates silently at @cap. Returns 0 on success,
|
||||
* -EINVAL if the source format isn't planar YUV 4:2:0.
|
||||
* Pack the decoded YUV planes into NV12M layout across two
|
||||
* mapped CAPTURE planes:
|
||||
* planes[0] = Y, written w bytes per row with stride dst_y_stride
|
||||
* planes[1] = interleaved CbCr at half-res, two bytes per chroma
|
||||
* sample, written cw*2 bytes per row with stride
|
||||
* dst_uv_stride
|
||||
*
|
||||
* Source stride padding (fr->linesize[*]) is stripped; destination
|
||||
* stride padding (dst_stride - row_bytes) is left as-is — the V4L2
|
||||
* client knows the format's bytesperline and walks accordingly.
|
||||
*
|
||||
* Returns 0 on success, -EINVAL if the source is not planar 4:2:0
|
||||
* (Phase 8.6 still expects yuv420p-class outputs; 8.7 widens).
|
||||
*/
|
||||
static int pack_nv12(struct AVFrame *fr, const AVPixFmtDescriptor *desc,
|
||||
uint8_t *out, size_t cap, size_t *used)
|
||||
static int pack_nv12_to_planes(struct AVFrame *fr,
|
||||
const AVPixFmtDescriptor *desc,
|
||||
const struct daedalus_capture_planes *planes)
|
||||
{
|
||||
int y, cw, ch, h, w;
|
||||
size_t y_size, uv_size;
|
||||
int h = fr->height;
|
||||
int w = fr->width;
|
||||
int cw, ch;
|
||||
size_t row_y, row_uv;
|
||||
int y, x;
|
||||
uint8_t *dst_y, *dst_uv;
|
||||
uint32_t dst_y_stride, dst_uv_stride;
|
||||
|
||||
*used = 0;
|
||||
if (!desc || !out || !cap)
|
||||
if (!desc || !planes || planes->nr < 2)
|
||||
return -EINVAL;
|
||||
if (desc->nb_components < 3)
|
||||
return -EINVAL;
|
||||
if (desc->log2_chroma_w != 1 || desc->log2_chroma_h != 1)
|
||||
return -EINVAL; /* not 4:2:0 — would need a different pack */
|
||||
|
||||
w = fr->width;
|
||||
h = fr->height;
|
||||
cw = AV_CEIL_RSHIFT(w, desc->log2_chroma_w);
|
||||
ch = AV_CEIL_RSHIFT(h, desc->log2_chroma_h);
|
||||
y_size = (size_t) w * (size_t) h;
|
||||
uv_size = (size_t) cw * (size_t) ch * 2u;
|
||||
|
||||
/* Y plane: w bytes per line stripped of stride padding. */
|
||||
if (*used + y_size > cap)
|
||||
dst_y = planes->base[0];
|
||||
dst_uv = planes->base[1];
|
||||
dst_y_stride = planes->stride[0] ? planes->stride[0] : (uint32_t) w;
|
||||
dst_uv_stride = planes->stride[1] ? planes->stride[1] : (uint32_t) (cw * 2);
|
||||
|
||||
row_y = (size_t) w;
|
||||
row_uv = (size_t) cw * 2u;
|
||||
|
||||
if (!dst_y || !dst_uv)
|
||||
return -EINVAL;
|
||||
|
||||
/* Y plane copy — strip source stride padding. */
|
||||
for (y = 0; y < h; y++)
|
||||
memcpy(out + *used + (size_t) y * (size_t) w,
|
||||
fr->data[0] + (size_t) y * (size_t) fr->linesize[0],
|
||||
(size_t) w);
|
||||
*used += y_size;
|
||||
memcpy(dst_y + (size_t) y * dst_y_stride,
|
||||
fr->data[0] + (size_t) y * fr->linesize[0],
|
||||
row_y);
|
||||
|
||||
/* Interleave Cb and Cr into a single NV12 chroma plane. */
|
||||
if (*used + uv_size > cap)
|
||||
return -EINVAL;
|
||||
{
|
||||
uint8_t *uv = out + *used;
|
||||
int x;
|
||||
for (y = 0; y < ch; y++) {
|
||||
const uint8_t *u = fr->data[1] +
|
||||
(size_t) y * (size_t) fr->linesize[1];
|
||||
const uint8_t *v = fr->data[2] +
|
||||
(size_t) y * (size_t) fr->linesize[2];
|
||||
for (x = 0; x < cw; x++) {
|
||||
uv[(y * cw + x) * 2 + 0] = u[x];
|
||||
uv[(y * cw + x) * 2 + 1] = v[x];
|
||||
}
|
||||
/* Interleave Cb and Cr into NV12 chroma plane. */
|
||||
for (y = 0; y < ch; y++) {
|
||||
const uint8_t *u = fr->data[1] +
|
||||
(size_t) y * fr->linesize[1];
|
||||
const uint8_t *v = fr->data[2] +
|
||||
(size_t) y * fr->linesize[2];
|
||||
uint8_t *row = dst_uv + (size_t) y * dst_uv_stride;
|
||||
for (x = 0; x < cw; x++) {
|
||||
row[x * 2 + 0] = u[x];
|
||||
row[x * 2 + 1] = v[x];
|
||||
}
|
||||
*used += uv_size;
|
||||
}
|
||||
(void) row_uv;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -191,8 +216,7 @@ int daedalus_decoder_run_request(struct daedalus_decoder *dec,
|
||||
const struct daedalus_req_decode *req,
|
||||
const uint8_t *bitstream,
|
||||
struct daedalus_resp_frame *resp,
|
||||
uint8_t *nv12_out, size_t nv12_cap,
|
||||
size_t *nv12_used)
|
||||
const struct daedalus_capture_planes *planes)
|
||||
{
|
||||
struct ffmpeg_loader *fm = dec->loader;
|
||||
struct AVCodecContext *ctx = NULL;
|
||||
@@ -200,8 +224,6 @@ int daedalus_decoder_run_request(struct daedalus_decoder *dec,
|
||||
|
||||
memset(resp, 0, sizeof(*resp));
|
||||
resp->codec_id = req->codec_id;
|
||||
if (nv12_used)
|
||||
*nv12_used = 0;
|
||||
|
||||
rc = decoder_open_codec(dec, req->codec_id, &ctx);
|
||||
if (rc == -ENOSYS) {
|
||||
@@ -315,25 +337,22 @@ int daedalus_decoder_run_request(struct daedalus_decoder *dec,
|
||||
resp->fnv1a_yuv = h;
|
||||
|
||||
/*
|
||||
* Pack pixels as NV12 into the caller's buffer if
|
||||
* provided and big enough. Truncation is silent —
|
||||
* the kernel will only copy as much as fits in the
|
||||
* CAPTURE plane, so partial fills are fine for Phase
|
||||
* 8.5. Phase 8.6 (dmabuf) eliminates the truncation.
|
||||
* Pack pixels as NV12 directly into the mapped CAPTURE
|
||||
* dmabuf planes. No copy into a wire buffer — pixels
|
||||
* land in the V4L2 client's CAPTURE buffer the moment
|
||||
* the write touches the mmap.
|
||||
*/
|
||||
if (nv12_out && nv12_cap) {
|
||||
int prc = pack_nv12(fr, desc, nv12_out, nv12_cap,
|
||||
nv12_used);
|
||||
if (planes && planes->nr >= 2) {
|
||||
int prc = pack_nv12_to_planes(fr, desc, planes);
|
||||
if (prc < 0)
|
||||
log_warn("decoder: NV12 pack failed (cap=%zu pix_fmt=%d) — kernel will see metadata only",
|
||||
nv12_cap, fr->format);
|
||||
log_warn("decoder: NV12-pack-to-planes failed (pix_fmt=%d planes=%d) — kernel will see metadata only",
|
||||
fr->format, planes->nr);
|
||||
}
|
||||
|
||||
log_info("decoder: OK %dx%d fmt=%d (%s) fnv1a=0x%08x luma=%u chroma=%u nv12=%zu",
|
||||
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,
|
||||
nv12_used ? *nv12_used : 0u);
|
||||
h, luma_len, chroma_len);
|
||||
}
|
||||
|
||||
fm->av_frame_unref(dec->frame);
|
||||
|
||||
+15
-13
@@ -15,6 +15,7 @@
|
||||
#include <stdint.h>
|
||||
|
||||
#include "daedalus_v4l2_proto.h"
|
||||
#include "dmabuf_capture.h"
|
||||
|
||||
struct ffmpeg_loader;
|
||||
struct AVCodecContext;
|
||||
@@ -26,12 +27,16 @@ struct AVFrame;
|
||||
* @loader: borrowed FFmpeg loader (must outlive the decoder)
|
||||
* @ctx_vp9: lazily-opened VP9 AVCodecContext (NULL until first
|
||||
* VP9 REQ_DECODE)
|
||||
* @ctx_av1: lazily-opened AV1 AVCodecContext
|
||||
* @ctx_h264: lazily-opened H.264 AVCodecContext
|
||||
* @pkt: shared AVPacket reused across requests
|
||||
* @frame: shared AVFrame reused across requests
|
||||
*/
|
||||
struct daedalus_decoder {
|
||||
struct ffmpeg_loader *loader;
|
||||
struct AVCodecContext *ctx_vp9;
|
||||
struct AVCodecContext *ctx_av1;
|
||||
struct AVCodecContext *ctx_h264;
|
||||
struct AVPacket *pkt;
|
||||
struct AVFrame *frame;
|
||||
};
|
||||
@@ -56,24 +61,21 @@ void daedalus_decoder_cleanup(struct daedalus_decoder *dec);
|
||||
* @req: REQ_DECODE prefix (from the wire)
|
||||
* @bitstream: bitstream blob (req->bitstream_len bytes)
|
||||
* @resp: caller-allocated RESP_FRAME output (zeroed by callee)
|
||||
* @nv12_out: caller-allocated buffer for NV12 pixel data
|
||||
* (Y plane followed by interleaved CbCr); may be NULL
|
||||
* if @nv12_cap is 0
|
||||
* @nv12_cap: bytes available at @nv12_out (truncated silently
|
||||
* if smaller than @resp->luma_len + @resp->chroma_len)
|
||||
* @nv12_used: out: bytes actually written into @nv12_out
|
||||
* @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.
|
||||
*
|
||||
* Populates @resp with the decode outcome and @nv12_out with the
|
||||
* NV12-packed pixels (Y plane, then interleaved Cb/Cr). Always
|
||||
* returns 0; decode-level failures are reported via @resp->status
|
||||
* and @nv12_used = 0 so the kernel sees a structured response
|
||||
* rather than a dropped request.
|
||||
* 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_run_request(struct daedalus_decoder *dec,
|
||||
const struct daedalus_req_decode *req,
|
||||
const uint8_t *bitstream,
|
||||
struct daedalus_resp_frame *resp,
|
||||
uint8_t *nv12_out, size_t nv12_cap,
|
||||
size_t *nv12_used);
|
||||
const struct daedalus_capture_planes *planes);
|
||||
|
||||
#endif /* DAEDALUS_V4L2_DECODER_H */
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
/* SPDX-License-Identifier: BSD-2-Clause */
|
||||
/*
|
||||
* dmabuf_capture.c — daemon-side CAPTURE-plane dmabuf access.
|
||||
*/
|
||||
#include "dmabuf_capture.h"
|
||||
#include "log.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/mman.h>
|
||||
|
||||
int daedalus_capture_planes_open(int chardev_fd, uint32_t cookie,
|
||||
const struct daedalus_req_decode *req,
|
||||
struct daedalus_capture_planes *out)
|
||||
{
|
||||
int i;
|
||||
int rc;
|
||||
|
||||
memset(out, 0, sizeof(*out));
|
||||
for (i = 0; i < DAEDALUS_MAX_CAPTURE_PLANES; i++)
|
||||
out->fd[i] = -1;
|
||||
|
||||
if (req->capture_num_planes == 0 ||
|
||||
req->capture_num_planes > DAEDALUS_MAX_CAPTURE_PLANES) {
|
||||
log_err("dmabuf_capture: bad num_planes=%u",
|
||||
req->capture_num_planes);
|
||||
return -EINVAL;
|
||||
}
|
||||
out->nr = (int) req->capture_num_planes;
|
||||
|
||||
for (i = 0; i < out->nr; i++) {
|
||||
struct daedalus_get_dmabuf gd = {
|
||||
.cookie = cookie,
|
||||
.plane = (uint32_t) i,
|
||||
/*
|
||||
* O_RDWR is required so the kernel exports the
|
||||
* dmabuf in a mode that allows PROT_READ |
|
||||
* PROT_WRITE mmap. vb2_core_expbuf extracts
|
||||
* the access mode (O_ACCMODE) from flags and
|
||||
* hands it to the memop's get_dmabuf; default
|
||||
* (O_RDONLY) gives mmap -EACCES on writes.
|
||||
*/
|
||||
.flags = O_RDWR | O_CLOEXEC,
|
||||
.fd = -1,
|
||||
};
|
||||
size_t size = req->capture_plane_size[i];
|
||||
|
||||
if (size == 0) {
|
||||
log_err("dmabuf_capture: cookie=%u plane=%d size=0",
|
||||
cookie, i);
|
||||
rc = -EINVAL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (ioctl(chardev_fd, DAEDALUS_IOC_GET_DMABUF, &gd) < 0) {
|
||||
rc = -errno;
|
||||
log_err("dmabuf_capture: GET_DMABUF cookie=%u plane=%d: %s",
|
||||
cookie, i, strerror(errno));
|
||||
goto fail;
|
||||
}
|
||||
out->fd[i] = gd.fd;
|
||||
out->size[i] = size;
|
||||
out->stride[i] = req->capture_plane_stride[i];
|
||||
|
||||
out->base[i] = mmap(NULL, size, PROT_READ | PROT_WRITE,
|
||||
MAP_SHARED, gd.fd, 0);
|
||||
if (out->base[i] == MAP_FAILED) {
|
||||
rc = -errno;
|
||||
log_err("dmabuf_capture: mmap cookie=%u plane=%d size=%zu: %s",
|
||||
cookie, i, size, strerror(errno));
|
||||
out->base[i] = NULL;
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
daedalus_capture_planes_close(out);
|
||||
return rc;
|
||||
}
|
||||
|
||||
void daedalus_capture_planes_close(struct daedalus_capture_planes *p)
|
||||
{
|
||||
int i;
|
||||
if (!p)
|
||||
return;
|
||||
for (i = 0; i < DAEDALUS_MAX_CAPTURE_PLANES; i++) {
|
||||
if (p->base[i] && p->base[i] != MAP_FAILED) {
|
||||
munmap(p->base[i], p->size[i]);
|
||||
p->base[i] = NULL;
|
||||
}
|
||||
if (p->fd[i] >= 0) {
|
||||
close(p->fd[i]);
|
||||
p->fd[i] = -1;
|
||||
}
|
||||
}
|
||||
p->nr = 0;
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/* SPDX-License-Identifier: BSD-2-Clause */
|
||||
/*
|
||||
* dmabuf_capture.h — daemon-side CAPTURE-plane dmabuf access.
|
||||
*
|
||||
* For every REQ_DECODE the daemon fetches a dmabuf fd per
|
||||
* CAPTURE plane via the chardev DAEDALUS_IOC_GET_DMABUF ioctl,
|
||||
* mmaps it, then asks the decoder to write decoded pixels
|
||||
* directly into the mapping. No inline pixel data in the
|
||||
* chardev payload — Phase 8.6's reason for being.
|
||||
*/
|
||||
#ifndef DAEDALUS_V4L2_DMABUF_CAPTURE_H
|
||||
#define DAEDALUS_V4L2_DMABUF_CAPTURE_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "daedalus_v4l2_proto.h"
|
||||
|
||||
#define DAEDALUS_MAX_CAPTURE_PLANES 3
|
||||
|
||||
/**
|
||||
* struct daedalus_capture_planes - mapped CAPTURE buffer planes
|
||||
* @nr: number of valid planes (0..DAEDALUS_MAX_CAPTURE_PLANES)
|
||||
* @fd: dmabuf fd per plane; -1 if not opened
|
||||
* @base: mmap'd pointer per plane; NULL if not mapped
|
||||
* @size: mapped length per plane (== capture_plane_size[i])
|
||||
* @stride: bytes-per-line per plane (from V4L2 fmt)
|
||||
*/
|
||||
struct daedalus_capture_planes {
|
||||
int nr;
|
||||
int fd[DAEDALUS_MAX_CAPTURE_PLANES];
|
||||
void *base[DAEDALUS_MAX_CAPTURE_PLANES];
|
||||
size_t size[DAEDALUS_MAX_CAPTURE_PLANES];
|
||||
uint32_t stride[DAEDALUS_MAX_CAPTURE_PLANES];
|
||||
};
|
||||
|
||||
/**
|
||||
* daedalus_capture_planes_open() - GET_DMABUF + mmap each plane
|
||||
* @chardev_fd: open /dev/daedalus-v4l2 fd
|
||||
* @cookie: cookie from REQ_DECODE (passes to GET_DMABUF)
|
||||
* @req: REQ_DECODE payload (carries plane count + sizes + strides)
|
||||
* @out: zeroed on entry; populated on success
|
||||
*
|
||||
* On any failure, partial state is rolled back and *out is
|
||||
* left zeroed. Return: 0 on success, -errno on failure.
|
||||
*/
|
||||
int daedalus_capture_planes_open(int chardev_fd, uint32_t cookie,
|
||||
const struct daedalus_req_decode *req,
|
||||
struct daedalus_capture_planes *out);
|
||||
|
||||
/**
|
||||
* daedalus_capture_planes_close() - munmap + close every plane
|
||||
*/
|
||||
void daedalus_capture_planes_close(struct daedalus_capture_planes *p);
|
||||
|
||||
#endif /* DAEDALUS_V4L2_DMABUF_CAPTURE_H */
|
||||
Reference in New Issue
Block a user