Skip to content

feat(planecontrol,videodecoder)!: the capture plane owns capture end to end (#753) - #761

Open
Ulrond wants to merge 13 commits into
developfrom
feature/753-planecontrol-capture-interface
Open

feat(planecontrol,videodecoder)!: the capture plane owns capture end to end (#753)#761
Ulrond wants to merge 13 commits into
developfrom
feature/753-planecontrol-capture-interface

Conversation

@Ulrond

@Ulrond Ulrond commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator

Closes #753.

What this adds

A decoded-frame capture surface on com.rdk.hal.planecontrol, so an application can import decoded video frames as GPU textures (EGL DMA-BUF import) while the decoder runs continuously. Each port does this today through a different vendor-private path, so middleware cannot state a portable contract.

Capture is declared, configured and bound entirely on the capture plane. IVideoDecoder carries nothing about it.

Capture is a plane type

PlaneType.CAPTURE sits beside VIDEO and GRAPHICS. The type says where a plane's pixels come from and where they go:

Type Pixels come from Pixels go to Interface
VIDEO A mapped video source The display IPlaneControl
GRAPHICS The client, frame by frame The display IGraphicsFbProvider
CAPTURE A mapped video source The client, frame by frame ICapture

A capture plane runs opposite to a graphics plane: a graphics plane carries frames from the client to the display, a capture plane carries decoded frames from the pipeline to the client. It is never composited, so alpha, z-order, position and display latency have no meaning on it.

// IPlaneControl
@nullable ICapture getCapture(in int planeResourceIndex, in ICaptureEventListener captureEventListener);

This mirrors getGraphicsFbProvider() exactly — one published service for the module, the capture resource enumerated by the existing plane enumeration. There is no ICaptureManager and no ICapture.Id.

The mapping is the binding

setVideoSourceDestinationPlaneMapping() maps a source to a CAPTURE plane exactly as it maps one to a display plane, and that mapping is the whole of the binding:

@nullable ICaptureController open(in ICaptureControllerListener captureControllerListener);

The source captured is whatever is mapped to the plane. A source is mapped to one plane at a time, which is what limits a decoder to a single capture session. Because the decoder is named in exactly one place, there is no second place for the two to disagree.

The vendor layer configures whatever the mapped source's decoder requires, over whatever internal path the platform provides.

Interface surface

Everything below is in com.rdk.hal.planecontrol.capture. Graphics moved to com.rdk.hal.planecontrol.graphics in the same pass, leaving com.rdk.hal.planecontrol for planes themselves.

File Role
ICapture.aidl Per-plane capture resource: getCapabilities(), getState(), open() / close()
ICaptureController.aidl Per-session: setFormat(), start(), stop(), acquireLatestFrame(), releaseFrame()
ICaptureControllerListener.aidl onPoolReady(), onFrameAvailable(), onCaptureError()
ICaptureEventListener.aidl onSystemError(), onSourceUnmapped(), onStateChanged()
CaptureCapabilities.aidl What a plane delivers and how its pool behaves
FormatLayout.aidl A pixel format paired with a memory layout valid for it
VideoBufferView.aidl Dma-Buf addressing of one pool buffer, delivered once per session
VideoFrameView.aidl One frame: buffer index and presentation time
State.aidl CLOSED, READY, STARTING, STARTED, STOPPING
CaptureErrorCode.aidl Failure reasons carried on EX_SERVICE_SPECIFIC
CaptureErrorCode.aidl Capture error codes
State.aidl planecontrol resource lifecycle states

Lifecycle: setVideoSourceDestinationPlaneMapping(source → capture plane)getCapture(planeIndex, eventListener)open(controllerListener)setProperty(WIDTH/HEIGHT/BUFFER_COUNT)start()acquireLatestFrame(releaseIndex)stop()close(controller).

Frame delivery

Addressing is delivered once. onPoolReady(VideoBufferView[]) carries every pool buffer with the file descriptors, offsets, strides, lengths, size, format and modifier that address it. None of that changes during a session, so the client imports each buffer into an EGLImage on receipt.

A frame is an index and a time. VideoFrameView is bufferIndex + presentationTimeNs, so a frame costs an int and a long on the wire rather than a ParcelFileDescriptor array per frame at 60 Hz.

Release and acquire are one call. acquireLatestFrame(releaseBufferIndex) frees the previous buffer and takes the next in one round trip. VideoFrameView.NO_BUFFER on the first call. releaseFrame() remains for the last frame of a session.

The frame returned is the one due for presentation. Audio latency and AV-sync correction are applied by the vendor layer, so a client that draws on receipt is in sync without computing anything. Frames whose presentation time has passed are dropped; frames whose time has not come stay queued.

planeFds[N] / planeOffsets[N] / planeStrides[N] feed EGL_DMA_BUF_PLANE<N>_FD_EXT / _OFFSET_EXT / _PITCH_EXT directly — no translation. A client caching EGLImages must key on bufferIndex, never on the file descriptor alone: under a shared-Dma-Buf pool every buffer carries the same descriptor and an fd-keyed cache silently freezes the picture.

What a plane declares, what a session configures

CaptureCapabilitiessupportedFormats (format and layout as pairs, since a modifier is valid with particular formats), supportedCodecs, maxFrameWidth / maxFrameHeight, resize, stallsWhenPoolExhausted. Nothing here is mandated. A platform that cannot capture a codec or a format does not list it, and a requirement it cannot meet would not change that.

The client makes one decision. It picks a row of supportedFormats and passes it to ICaptureController.setFormat(). That is required before start(), which fails with INVALID_CONFIGURATION otherwise — there is no default pair to assume once nothing is mandated.

Frame size is the plane's own Property.WIDTH / HEIGHT, set through IPlaneControl the way any plane's size is. Pool depth is not configured at all: the platform calibrates it from the throughput it can sustain, and the client learns it by counting what onPoolReady() delivers.

resize false means WIDTH / HEIGHT must equal what the mapped source decodes to, and start() fails with RESOLUTION_MISMATCH otherwise. Nothing is scaled, rotated, cropped, colour-converted or tone-mapped on this path — shape and colour belong to the consumer and may change on any frame.

Errors and lifetime

onCaptureError() reports failures not tied to a single acquire. CaptureErrorCode covers OUT_OF_MEMORY, SOURCE_NOT_MAPPED, CODEC_NOT_CAPTURABLE, HARDWARE_FAULT, RESOLUTION_MISMATCH, COLOR_CONVERSION_UNSUPPORTED, FORMAT_UNSUPPORTED and INVALID_CONFIGURATION. Nothing falls back to plane output.

plane_control.md specifies startup in either order (frames produced before start() are discarded, and starting capture may cost a decode interruption while the vendor reconfigures), shutdown in either order, and that imported EGLImages do not survive stop().

videodecoder

Its net change from develop is the removal of OperationalMode, IVideoDecoderManager.getSupportedOperationalModes() and Property.OPERATIONAL_MODE. Where a decoder's frames go follows from how it is wired — mapped to a display plane, returned over onFrameOutput(), or mapped to a capture plane — so there is no mode to select. video_decoder.md carries the routing table.

Product profile

hfp-planecontrol.yaml declares the DPI9 capture plane: FHD, NV12 linear, H264, resize: false, one plane. The interface stays general and the product declaration narrows it, so a later platform declares more without an interface change.

Points for reviewers

  1. onStateChanged sits on ICaptureEventListener, not the controller listener — videodecoder puts state changes on the event listener and frame callbacks on the controller listener.
  2. CaptureErrorCode is numbered compactly (1–8) rather than preserving holes. The enum is new here and has never shipped.
  3. resize is a capability, not a property. A plane either scales or it does not; that is a hardware fact, not a per-session choice.
  4. Buffer count is left to the vendor by default. No Netflix-defined count is known, and the vendor knows its own memory region and reference-frame needs.
  5. captureCapabilities is declared in the HFP, reachable through ICapture.getCapabilities()graphicsFbCapabilities works the same way.

Governance

New interface on a GREEN component, so reviewer sign-off in planecontrol/metadata.yaml and videodecoder/metadata.yaml is set to recheck for the 14+5 cycle.

Extends planecontrol with a graphics capture surface so an application can
import decoded video frames as GPU textures while the decoder runs
continuously, replacing the per-SoC vendor-private enable-texture paths.

Capture is a routing destination for a decoder's output, which is what this
module already owns, so ICapture is reached through IPlaneControl.getCapture()
on the video plane the decoder would otherwise have been mapped to - the same
idiom as getGraphicsFbProvider(). One published service for the module.

Surface:
  ICapture                    per-plane resource: capabilities, state,
                              property reads, open()/close()
  ICaptureController          per-session: start/stop, acquireLatestFrame,
                              releaseFrame, property writes
  ICaptureControllerListener  onRingReady, onFrameAvailable
  ICaptureEventListener       onSystemError, onDecoderDetached, onStateChanged
  CaptureCapabilities         ring limits, formats, modifiers, ring model
  CaptureProperty             ring shape property keys
  CapturePropertyKVPair       capture property key/value pair
  VideoFrameView              per-frame Dma-Buf addressing
  CaptureErrorCode            capture error codes
  State                       planecontrol resource lifecycle states

IVideoDecoder is unchanged - the decoder does not know where its output goes.
Capture requires OperationalMode.GRAPHICS_TEXTURE, which videodecoder already
advertises through IVideoDecoderManager.getSupportedOperationalModes().

Frames are NV12 linear with truthful per-plane offsets, importable through
EGL_EXT_image_dma_buf_import without translation. Decode proceeds at full rate
regardless of the consumer's acquire cadence; the all-slots-locked policy is
HFP-declared. An unsupported configuration fails start() with a CaptureErrorCode
rather than silently falling back to plane output.

Names are capture-prefixed where the module already defines the unprefixed
name (Property, PropertyKVPair, Capabilities).

hfp-planecontrol.yaml gains a per-plane captureCapabilities declaration.
Reviewer sign-off is set to recheck for the 14+5 cycle on the new interface.
Copilot AI lite review requested due to automatic review settings July 29, 2026 20:57
@github-project-automation github-project-automation Bot moved this to Architecture Review Required in halif_aidl Jul 29, 2026
@Ulrond Ulrond added component:planecontrol SOC component: planecontrol Minor Change Additive, backwards-compatible interface change — bumps minor; the default for real work labels Jul 29, 2026
@Ulrond Ulrond self-assigned this Jul 29, 2026
@Ulrond
Ulrond requested review from a team July 29, 2026 20:57
@Ulrond Ulrond moved this from Architecture Review Required to Under Review in halif_aidl Jul 29, 2026
@Ulrond Ulrond added this to the 0.23.0 - Auugst Mid milestone Jul 29, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extends the com.rdk.hal.planecontrol HAL with a new decoded-frame capture surface (ICapture) to support decode-to-texture workflows (DMA-BUF ring suitable for EGL DMA-BUF import) without changing IVideoDecoder.

Changes:

  • Adds a new per-plane capture API surface (ICapture + controller + listeners) and supporting types (capabilities, properties, error codes, frame view, lifecycle state).
  • Extends IPlaneControl with getCapture(planeResourceIndex, captureEventListener) to access capture as a PlaneControl sub-resource (Option B).
  • Updates the PlaneControl HFP, module metadata, and documentation to describe capture destinations and lifecycle.

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
planecontrol/metadata.yaml Updates component scope/notes and resets reviewer sign-off statuses for the new capture review cycle.
planecontrol/current/hfp-planecontrol.yaml Declares per-plane captureCapabilities in the HFP (slot limits, formats/modifiers, ring model, full-ring policy).
planecontrol/current/docs/plane_control.md Documents the new capture requirements, interfaces, and lifecycle/contract.
planecontrol/current/com/rdk/hal/planecontrol/IPlaneControl.aidl Adds getCapture(...) to expose capture per video plane resource.
planecontrol/current/com/rdk/hal/planecontrol/ICapture.aidl Defines the capture resource API (capabilities/state, open/close, property reads).
planecontrol/current/com/rdk/hal/planecontrol/ICaptureController.aidl Defines per-session control (start/stop/acquire/release, property writes).
planecontrol/current/com/rdk/hal/planecontrol/ICaptureControllerListener.aidl Adds callbacks for ring readiness and frame availability.
planecontrol/current/com/rdk/hal/planecontrol/ICaptureEventListener.aidl Adds resource-level callbacks for system errors, decoder detach, and state changes.
planecontrol/current/com/rdk/hal/planecontrol/CaptureCapabilities.aidl Adds the capability parcelable for ring and format/modifier support.
planecontrol/current/com/rdk/hal/planecontrol/CaptureProperty.aidl Adds the capture property key enum (slot/ring/format and dimensions).
planecontrol/current/com/rdk/hal/planecontrol/CapturePropertyKVPair.aidl Adds key/value pair parcelable for atomic multi-property writes.
planecontrol/current/com/rdk/hal/planecontrol/CaptureErrorCode.aidl Adds capture-specific service error codes for start/open/system-error reporting.
planecontrol/current/com/rdk/hal/planecontrol/VideoFrameView.aidl Adds per-frame DMA-BUF addressing parcelable for EGL import.
planecontrol/current/com/rdk/hal/planecontrol/State.aidl Adds capture resource lifecycle state enum (module-scoped).

Comment on lines +156 to +159
* The bound decoder must have `OperationalMode.GRAPHICS_TEXTURE` selected in its
* `Property.OPERATIONAL_MODE` before `ICaptureController.start()` is called. Whether
* that mode is available is advertised by
* `IVideoDecoderManager.getSupportedOperationalModes()`.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in ac45760 — qualified as videodecoder.OperationalMode.GRAPHICS_TEXTURE, videodecoder.Property.OPERATIONAL_MODE and videodecoder.IVideoDecoderManager.getSupportedOperationalModes() across all the capture AIDL and the module docs, not just this line.


The `IVideoDecoder` contract is unchanged - the decoder does not know where its output goes.

Capture requires the decoder to be operating in `OperationalMode.GRAPHICS_TEXTURE`, which is advertised by `IVideoDecoderManager.getSupportedOperationalModes()` and selected through the decoder's `Property.OPERATIONAL_MODE`. A video decoder can be bound to at most one capture session at a time.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in ac45760 — qualified as videodecoder.OperationalMode.GRAPHICS_TEXTURE, videodecoder.Property.OPERATIONAL_MODE and videodecoder.IVideoDecoderManager.getSupportedOperationalModes() across all the capture AIDL and the module docs, not just this line.

planecontrol defines its own Property enum, so an unqualified
Property.OPERATIONAL_MODE reads as planecontrol.Property. Qualify the
videodecoder-owned OperationalMode, Property and IVideoDecoderManager
references throughout the capture documentation.
Copilot AI review requested due to automatic review settings July 30, 2026 06:46

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (3)

planecontrol/current/com/rdk/hal/planecontrol/ICapture.aidl:164

  • The crash-cleanup description refers to ICaptureController.close(), but ICaptureController has no close() method (closing is done via ICapture.close(controller)). This makes the lifecycle contract ambiguous.
     * If the client that opened the `ICaptureController` crashes, then the
     * `ICaptureController` has `stop()` and `close()` implicitly called to perform clean up.

planecontrol/current/com/rdk/hal/planecontrol/ICaptureEventListener.aidl:41

  • Hyphenate “vendor-specific” in the parameter description for consistency and grammar.
     * @param[in] errorCode         A CaptureErrorCode enum value.
     * @param[in] vendorErrorCode   A vendor specific error code.
     */

planecontrol/current/com/rdk/hal/planecontrol/State.aidl:22

  • The @brief says this enum is for a “planecontrol resource instance”, but the following lines clarify it applies specifically to capture resources. Tighten the brief to avoid readers assuming plane resources have a lifecycle state.
 *  @brief     Lifecycle state of a planecontrol resource instance.

…r owns its output format

PlaneType.CAPTURE
-----------------
Capture is its own plane type rather than a capability bolted onto a video
plane. A capture plane is a plane whose destination is the client's texture
instead of the display, so it is discovered and addressed exactly as a display
plane is. It is never composited, so alpha, z-order and display latency do not
apply to it.

The decoder owns format and size
--------------------------------
Format was declared twice - once on the plane, once by the decoder - and
CaptureErrorCode.FORMAT_MISMATCH existed only because the two could disagree.
Format and size are properties of the decoder's output, so they move to
videodecoder and the error class disappears with them.

  videodecoder gains  CaptureConfig{drmFourcc, drmModifier, width, height}
                      IVideoDecoderController.setCaptureConfig()
                      Capabilities.supportedCaptureFourCCs / Modifiers

  planecontrol loses  CaptureCapabilities.supportedFourCCs / supportedModifiers
                      CaptureProperty.DRM_FOURCC / DRM_MODIFIER / WIDTH / HEIGHT
                      CaptureErrorCode.FORMAT_MISMATCH (ordinal 3 left as a gap)

A capture plane consumes what the decoder produces; it does not negotiate a
second format.

Routing is the mode
-------------------
Property.OPERATIONAL_MODE, the OperationalMode enum and
IVideoDecoderManager.getSupportedOperationalModes() are removed. Where a
decoder's frames go follows from how it is wired: mapped to a plane, returned
over onFrameOutput(), or routed to capture by setCaptureConfig(). That call is
the whole of capture-mode selection - a decoder with a configuration applied
emits for capture, one without does not, and it clears on close(). Whether a
decoder supports capture at all is Capabilities.supportedCaptureFourCCs being
non-empty.

Pool, not ring
--------------
The client-visible semantics were never a ring: acquireLatestFrame() takes the
newest Ready buffer and lets older ones go, so nothing is consumed in order.
Renamed throughout to match videodecoder's existing OUTPUT_FRAME_POOL_SIZE:

  maxSlotCount/maxSlotSizeBytes -> maxBufferCount
  SLOT_COUNT/SLOT_SIZE_BYTES    -> BUFFER_COUNT
  VideoFrameView.slot           -> bufferIndex
  stallsWhenRingFull            -> stallsWhenPoolExhausted
  onRingReady()                 -> onPoolReady()

sharedRingBuffer and onPoolReady's pool file descriptor are gone. Every frame
carries the descriptors and offsets that address it, so one Dma-Buf carved into
offset-addressed buffers and one Dma-Buf per buffer are served by identical
client code. CaptureCapabilities is now maxBufferCount and
stallsWhenPoolExhausted - the two things a client cannot observe for itself.

Documentation
-------------
video_decoder.md replaces "Operational Modes" with "Output Routing" and gains
"Decode to Texture" covering pixel format versus memory layout, the
vendor-namespaced modifier encoding, and why the choice trades bandwidth
against portability - a GPU that reads the vendor's compressed layout can halve
capture-path bandwidth, while anything touching pixels needs LINEAR, which is
why NV12 + LINEAR is required of every decoder that supports capture.

Reviewer sign-off on both components is set to recheck.
Copilot AI review requested due to automatic review settings July 31, 2026 13:00
@Ulrond Ulrond changed the title feat(planecontrol): add ICapture decoded-frame capture interface (#753) feat(planecontrol,videodecoder)!: capture is a plane type; the decoder owns its output format (#753) Jul 31, 2026
@Ulrond Ulrond added component:videodecoder SOC component: videodecoder Major Change Breaking interface change (renames/removals/signature changes) — bumps major and removed Minor Change Additive, backwards-compatible interface change — bumps minor; the default for real work labels Jul 31, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 24 out of 24 changed files in this pull request and generated 2 comments.

Suppressed comments (3)

planecontrol/current/com/rdk/hal/planecontrol/ICaptureController.aidl:62

  • The start() documentation references CaptureProperty.BUFFER_SIZE_BYTES, but CaptureProperty currently only defines BUFFER_COUNT. This makes the API contract internally inconsistent.
     * Reserves a pool of `CaptureProperty.BUFFER_COUNT` buffers of
     * `CaptureProperty.BUFFER_SIZE_BYTES` each from the platform's video memory region,
     * and wires the bound video decoder's capture output into the pool.

planecontrol/current/com/rdk/hal/planecontrol/IPlaneControl.aidl:259

  • This method returns a capture interface for a PlaneType.CAPTURE plane, but the brief says "for a video plane resource". That wording is inaccurate and may confuse consumers into thinking capture is a subtype of VIDEO planes rather than its own plane type.
     * Gets a Capture interface for a video plane resource.

videodecoder/current/com/rdk/hal/videodecoder/IVideoDecoderController.aidl:353

  • The EX_ILLEGAL_ARGUMENT documentation refers to drmFourcc, drmModifier, width and height as if they were direct parameters, but this method takes a single captureConfig object. This is misleading for API consumers and makes the contract harder to interpret.
     * @exception binder::Status::Exception::EX_ILLEGAL_ARGUMENT if `drmFourcc` is not in
     *            `Capabilities.supportedCaptureFourCCs`, `drmModifier` is not in
     *            `Capabilities.supportedCaptureModifiers`, or `width`/`height` exceed the
     *            `CodecCapabilities` of the codec this decoder was opened for.

@@ -50,24 +49,6 @@ interface IVideoDecoderManager
*/
IVideoDecoder.Id[] getVideoDecoderIds();

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same answer as on Property.OPERATIONAL_MODE — deliberate, marked !, and the capability moves to the capture plane, which owns capture end to end after #753. A client on the old surface pins the frozen videodecoder/0.1.0.0 or 0.2.0.0 snapshots, which are untouched.

@@ -66,26 +66,6 @@ enum Property {
*/
OUTPUT_FRAME_POOL_SIZE = 2,

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deliberate, and marked ! on both the commit and the PR title. Two things make it a versioned removal rather than silent breakage:

  • The ordinal is retired, not reused. Property now runs … 2, 4, 5 …, so no existing member's value shifts and no client misreads a value it still knows.
  • current is the development surface. videodecoder is pre-baseline at 0.2.0.1, and a client needing the old surface pins the frozen videodecoder/0.1.0.0 or 0.2.0.0 snapshots, which this PR does not touch.

OPERATIONAL_MODE and getSupportedOperationalModes() move to the capture plane, which now owns capture end to end — that is the substance of #753 rather than a side effect of it. Capture is selected by mapping a source to a plane of type CAPTURE, so there is no longer a mode to set on the decoder.

…full

A requirements review against the graphics-player specification found one
contradiction and four unstated obligations.

THE DECODER MUST NOT TRANSFORM THE FRAME
----------------------------------------
CaptureConfig.width and .height read as "the width of the captured frames",
which invites a vendor to scale output to them. The specification requires the
opposite: frames arrive at the resolution the stream decodes to, in the source
colorimetry, with no scaling, rotation, crop, colour conversion, tone-mapping
or gamma adjustment.

Shape and colour belong to the consumer, which applies them per frame as it
textures the frame onto its scene and may change them on any frame. A transform
applied in the decoder would have to be undone, and one the consumer cannot
undo makes the frame unusable.

So the fields are restated as what they are - the maximum dimensions the
buffers must accommodate - and VideoFrameView.width/.height now say they report
what each frame actually is, which a smaller stream makes differ.

  HAL.PLANECONTROL.14, HAL.VIDEODECODER.16

FRAME-DROP BUDGET
-----------------
No more than one dropped frame per 15 seconds of capture, 144p through 2160p,
while the client acquires and releases at the presentation cadence. The capture
path is not permitted to lose frames a display plane would have shown. A client
that stops releasing is explicitly not covered - that case is
CaptureCapabilities.stallsWhenPoolExhausted.

  HAL.PLANECONTROL.15

PRESENTATION TIME
-----------------
Carried unaltered, and stated as the frame's only timing reference. A captured
frame goes to the client's scene rather than to a display plane, so the client
presents it against the clock its audio path already runs on.

  HAL.PLANECONTROL.16

CONCURRENCY AND ALPHA
---------------------
Capture planes and video planes are independent resources, so a product
declaring both runs a capture session alongside a playback session routed to a
display plane, up to the decoder count its video decoder profile declares.

NV12 and DRM_FORMAT_MOD_LINEAR are the required baseline, not the limit -
supportedCaptureFourCCs is an open list, so a product able to emit a format
carrying alpha declares it and a client selects it, with no interface change.
Copilot AI review requested due to automatic review settings August 3, 2026 10:58
@Ulrond

Ulrond commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator Author

Finding — capture does not relay colour metadata it already has

Raising this separately because it is a pre-merge gap with no detectable failure mode, and it is the one thing on this PR I would not merge without a decision on.

What is missing. A capture plane specifies the memory layout of the frames it delivers and nothing about how to interpret their pixels. Not in VideoBufferView, not in CaptureProperty, not in CaptureCapabilities.

Why it matters. A consumer importing NV12 through EGL_EXT_image_dma_buf_import supplies the conversion parameters in the attribute list, and the external-texture sampler uses what it is given:

EGL_YUV_COLOR_SPACE_HINT_EXT               EGL_ITU_REC601_EXT / REC709_EXT / REC2020_EXT
EGL_SAMPLE_RANGE_HINT_EXT                  EGL_YUV_FULL_RANGE_EXT / EGL_YUV_NARROW_RANGE_EXT
EGL_YUV_CHROMA_HORIZONTAL_SITING_HINT_EXT  EGL_YUV_CHROMA_SITING_0_EXT / _0_5_EXT
EGL_YUV_CHROMA_VERTICAL_SITING_HINT_EXT    EGL_YUV_CHROMA_SITING_0_EXT / _0_5_EXT

Omit them and the driver falls back to its own default, typically BT.601 narrow. HD and UHD content is BT.709 or BT.2020, so the result is shifted colour and crushed or stretched levels. Wrong colour renders a plausible picture, not an error — no return code, no log line, and no application-side test can catch it, because the consumer has no reference to compare against. It surfaces as a subjective PQ complaint late in certification and is expensive to trace back to an unset EGL attribute.

drmFourcc does not answer it. DRM_FORMAT_NV12 states the sample layout and says nothing about primaries, transfer or range; the same fourcc is correct for BT.601 SD and BT.2020 UHD alike.

The type already exists one module across. videodecoder defines Colorimetry { range, matrix, transfer, primaries }, CICP-valued, and the decoder already produces it per frame in FrameMetadata.colorimetry, with Capabilities.supportedColorimetries declaring what it can detect. The value is known on the producer side at the moment each frame is written; it is simply not relayed. planecontrol already imports videodecoder types where the concept is the decoder's — CaptureCapabilities imports Codec, PlaneCapabilities imports PixelFormat and DynamicRange — so this is the established pattern, not a new coupling.

DynamicRange does not close it: it is SDR / HLG / HDR10 / HDR10_PLUS / DOLBY_VISION, a classification of what a plane supports, and it cannot express BT.709 vs BT.2020 matrix or limited vs full range.

What is genuinely new: chroma siting. It appears nowhere in videodecoder or planecontrol — zero occurrences. Wrong siting is a half-sample chroma shift: subtler than a wrong matrix, equally undetectable from application code, visible on high-contrast colour edges.

To decide

  1. Per frame or per pool. The producer models colorimetry per frame (FrameMetadata.colorimetry). Relaying it once at onPoolReady() on VideoBufferView narrows that, and would pin the first value if colorimetry changes mid-session — an ad break or a profile switch, not only a source switch. Four enums is 16 bytes with no descriptors, so the cost argument that keeps addressing out of VideoFrameView does not apply here. I would put it on VideoFrameView and follow the producer, unless we can state why the narrowing is safe.
  2. What UNKNOWN means. All four Colorimetry fields default to UNKNOWN. If the contract stops at "relay it", an implementation can relay UNKNOWN for everything, the client leaves the attribute unset, and we are back on the driver's BT.601 default — the exact bug. The contract needs a stated fallback.
  3. How chroma siting is represented. A new enum in videodecoder beside the other colour types, so it serves any consumer of FrameMetadata and not only capture.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 24 out of 24 changed files in this pull request and generated no new comments.

Suppressed comments (5)

videodecoder/current/com/rdk/hal/videodecoder/Property.aidl:69

  • Property.OPERATIONAL_MODE was removed from the current videodecoder property enum but still exists in the released videodecoder/0.2.0.1 API. Removing an enum constant/property is a breaking change for any client that reads/writes it; consider deprecating it (documenting it as ignored) or cutting a new versioned release/interface for the updated routing model.
    OUTPUT_FRAME_POOL_SIZE = 2,

	/**

videodecoder/current/com/rdk/hal/videodecoder/IVideoDecoderManager.aidl:53

  • getSupportedOperationalModes() was removed from the current IVideoDecoderManager API, but it still exists in the released videodecoder/0.2.0.1 interface. This is a breaking interface change for clients built against that release; consider keeping the method (even if deprecated) or introducing a new versioned interface/release rather than removing the contract outright.
	IVideoDecoder.Id[] getVideoDecoderIds();

    /**
	 * Gets a Video Decoder interface.

planecontrol/current/com/rdk/hal/planecontrol/ICapture.aidl:198

  • The comment says the ICaptureController has close() called implicitly on client crash, but close() is a method on ICapture, not on ICaptureController. This is confusing for API consumers and should describe the session being implicitly stopped and closed.
     * If the client that opened the `ICaptureController` crashes, then the
     * `ICaptureController` has `stop()` and `close()` implicitly called to perform clean up.

planecontrol/current/com/rdk/hal/planecontrol/IPlaneControl.aidl:265

  • This method returns an ICapture for a PlaneType.CAPTURE resource, but the first line of the docstring says “video plane resource”, which is misleading (a capture plane is not a video/display plane).
     * Gets a Capture interface for a video plane resource.

videodecoder/current/docs/video_decoder.md:242

  • This row implies IPlaneControl.getCapture() alone opens a capture session “against this decoder”, but capture is actually routed by mapping the decoder’s source to a CAPTURE plane and then opening ICapture on that plane. Clarifying this avoids readers assuming a direct decoder→capture binding API exists in videodecoder.
| **A capture plane** | The client opens a capture session against this decoder through `IPlaneControl.getCapture()`. | No. Frames are consumed through the capture plane. |

Copilot AI review requested due to automatic review settings August 14, 2026 17:49
@Ulrond

Ulrond commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator Author

Resolved in 6608521f. Updating my comment above, which raised these as open questions — they are now decided and implemented.

A capture plane was declared twice. PlaneCapabilities and CaptureCapabilities both stated its formats — videodecoder.PixelFormat against DRM FOURCC + modifier — and both stated its size ceiling, maxWidth/maxHeight against maxFrameWidth/maxFrameHeight (that second pair I missed first time round). PlaneType.CAPTURE retired alpha, z-order, position, size and display latency and left the format fields standing, so on the face of the interface all of them still applied.

CaptureCapabilities now governs, because it is the only one of the two that can state the answer: an EGL_EXT_image_dma_buf_import client needs a FOURCC and a modifier, and PixelFormat has no modifier. On a capture plane PlaneCapabilities describes routing — planeIndex, type, sourceTypes — and every other field shall be empty, zero or false. Stated as an obligation rather than as "has no meaning": a field left to mean nothing gets populated anyway and read anyway, whereas zero is visibly unset and a VTS test can assert it. Both duplicate pairs then dissolve rather than needing a precedence rule, because only one side of each is ever populated.

Neither declaration said what the pixels mean. NV12 states the sample layout and nothing about the primaries, transfer, matrix or range the samples were coded against — the same fourcc is correct for BT.601 SD and BT.2020 UHD alike. A consumer that omits the EGL conversion attributes gets the driver's default, typically BT.601 narrow, and renders a plausible picture in the wrong colour with no return code, no log line and nothing to compare against.

videodecoder already had the type and the decoder already produces it per frame, so:

  • CaptureCapabilities.supportedColorimetries[] — the capability, read before start().
  • VideoFrameView.colorimetry — the value, per frame, because an ad break or a profile switch changes it without changing the source and a value fixed at onPoolReady() would pin the first one silently. Four enums, no file descriptors.
  • Declaring the capability declares an obligation: a plane with a non-empty array populates every field on every frame, and an UNKNOWN from such a plane is a conformance failure rather than a value.
  • An empty array means the plane cannot report colorimetry, and the fallback is stated once so every consumer assumes the same thing — BT.601 limited to 576 lines, BT.709 limited to 1080, BT.2020 NCL limited above.

Chroma siting had no representation anywhere. ChromaSite and ChromaSitePosition are new in videodecoder, carried on FrameMetadata and VideoFrameView. Kept out of Colorimetry, which maps field by field onto GstVideoColorimetry — GStreamer keeps GstVideoChromaSite as its own type for the same reason.

planecontrol and videodecoder both build clean.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 27 out of 27 changed files in this pull request and generated 1 comment.

Suppressed comments (2)

planecontrol/current/com/rdk/hal/planecontrol/IPlaneControl.aidl:265

  • The docstring says this returns a Capture interface for a "video plane resource", but this API is specifically for plane resources of type PlaneType.CAPTURE. Calling it a video plane is misleading for API consumers.
     * Gets a Capture interface for a video plane resource.

planecontrol/current/com/rdk/hal/planecontrol/PlaneCapabilities.aidl:37

  • This comment states that on PlaneType.CAPTURE planes, all non-routing fields (including maxWidth/maxHeight, frameWidth/frameHeight, etc.) "shall" be empty/zero/false. However, the product HFP added in this PR populates those fields for the capture plane, which would contradict this API contract. Consider narrowing this requirement to only the truly display/compositing-related fields (alpha/z-order/vsync latency, etc.), or otherwise clarify how these fields are expected to be used for capture planes.
 *  Every other field of this parcelable shall be EMPTY, ZERO OR FALSE on a capture
 *  plane - `pixelFormats` and `supportedDynamicRanges` empty, `colorDepth`,
 *  `maxWidth`, `maxHeight`, `frameWidth`, `frameHeight`, `maxFrameRate` and
 *  `vsyncDisplayLatency` zero, `supportsAlpha` and `supportsZOrder` false.

* by field onto GstVideoColorimetry, where siting is a separate concern and
* GStreamer keeps GstVideoChromaSite as its own type.
*/
ChromaSite chromaSite;
Copilot AI review requested due to automatic review settings August 14, 2026 18:51
@Ulrond

Ulrond commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator Author

Withdrawing my previous comment. 6608521f is reverted in f7ca4723 — the branch is back to 1311c80c and nothing from that proposal stands.

It changed videodecoder, adding ChromaSite and ChromaSitePosition types and a chromaSite field on FrameMetadata. That module is not in scope for this change and I should not have pushed it. FrameMetadata.aidl is now byte-identical to develop again, and this branch touches videodecoder only through the three commits that predate this — 4739763c, d5945252 and 6db6d866.

The gap raised in #761 (comment) is still open, and the questions in it stand:

  1. Which parcelable governs a capture plane's declaration, given PlaneCapabilities and CaptureCapabilities both state its formats and its size ceiling with no stated precedence.
  2. Whether colorimetry is carried per frame or once with the pool.
  3. What an implementation owes when a colorimetry field is UNKNOWN.
  4. Where chroma siting lives — and specifically whether it can be expressed without changing videodecoder, since siting has no representation in either module today.

Nothing further will be implemented against this until those are agreed.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 24 out of 24 changed files in this pull request and generated no new comments.

Suppressed comments (4)

planecontrol/current/docs/plane_control.md:68

  • This entry says capture capabilities are "for a video plane", but CaptureCapabilities describes the PlaneType.CAPTURE contract. Using "capture plane" here avoids confusion with PlaneType.VIDEO.
| `CaptureCapabilities.aidl` | Parcelable describing capture capabilities for a video plane.|

planecontrol/current/com/rdk/hal/planecontrol/IPlaneControl.aidl:266

  • The docstring says "video plane resource", but this method only applies to plane resources of type PlaneType.CAPTURE (capture planes). This wording is misleading for API consumers.
    /**
     * Gets a Capture interface for a video plane resource.
     *

planecontrol/current/docs/plane_control.md:60

  • This table entry describes ICapture as for a "video plane"; capture is explicitly for planes of type PlaneType.CAPTURE (capture planes), not video planes.

This issue also appears on line 68 of the same file.

| `ICapture.aidl` | Decoded frame capture interface for a video plane used as a capture destination.|

planecontrol/current/docs/plane_control.md:256

  • The section text still says there are "2 types of planes (video and graphics)", but this PR adds PlaneType.CAPTURE and the table now includes Capture. Update the sentence above this table so the documentation stays consistent.
|-----------|--------------------|
| **Video** |If there is no video to display on a visible plane, then it shall render transparent black. <br>The z-order is dynamic only for video planes.<br> Primary video plane shall always be listed at resource index 0.|
| **Graphics** |When the plane type is GRAPHICS, `getGraphicsFbProvider()` provides graphics frame creation, commit, and destroy operations.|
| **Capture** |The destination is the client's texture rather than the display, so the plane is never composited: alpha, z-order and display latency do not apply.<br>The source is mapped with `setVideoSourceDestinationPlaneMapping()` exactly as it is for a video plane, and that mapping is what routes the source to capture.<br>When the plane type is CAPTURE, `getCapture()` provides decoded frame capture to a Dma-Buf buffer pool.<br>It runs opposite to a graphics plane: a graphics plane carries frames from the client to the display, a capture plane carries decoded frames from the pipeline to the client.<br>Capture planes are listed after graphics planes.|

Copilot AI review requested due to automatic review settings August 15, 2026 11:17
@Ulrond
Ulrond force-pushed the feature/753-planecontrol-capture-interface branch 2 times, most recently from e5fc087 to 8b72315 Compare August 15, 2026 11:17

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 29 out of 29 changed files in this pull request and generated no new comments.

Suppressed comments (13)

planecontrol/current/hfp-planecontrol.yaml:124

  • The capture HFP declares supportedFourCCs and supportedModifiers as independent lists, but the capture AIDL surface declares paired supportedFormats (FormatLayout[]). This mismatch makes it ambiguous how a product profile maps onto the runtime CaptureCapabilities contract.
          # FOURCC codes and modifiers are the kernel's, from include/uapi/drm/drm_fourcc.h.
          supportedFourCCs:     # DRM_FORMAT_NV12 required on every capture plane
            - 0x3231564E        # DRM_FORMAT_NV12
          supportedModifiers:   # DRM_FORMAT_MOD_LINEAR required on every capture plane
            - 0x0000000000000000  # DRM_FORMAT_MOD_LINEAR

planecontrol/current/hfp-planecontrol.yaml:131

  • supportedCodecs uses H264, but the com.rdk.hal.videodecoder.Codec enum uses H264_AVC. Using a value that doesn't exist in the codec enum will break schema-to-AIDL alignment and makes the profile ambiguous.
            - H264

planecontrol/current/com/rdk/hal/planecontrol/capture/CaptureCapabilities.aidl:59

  • CaptureCapabilities refers to Property.DRM_FOURCC / Property.DRM_MODIFIER, but this interface exposes format selection via ICaptureController.setFormat(FormatLayout) and there is no such Property here. This reads like leftover text from an earlier design and can mislead API consumers.
     * A client selects one entry and sets `Property.DRM_FOURCC` and
     * `Property.DRM_MODIFIER` from it.

planecontrol/current/com/rdk/hal/planecontrol/capture/CaptureCapabilities.aidl:91

  • The comment states Codec.H264_AVC and Codec.AV1 are both required, but the product HFP in this PR declares only H.264 for the capture plane and the PR description also calls out H.264 as the required baseline. Please align the contract text so requirements and profiles don't contradict each other.
     * `Codec.H264_AVC` and `Codec.AV1` are both required to be present on every
     * capture plane. Certification asks for H.264 in one cycle and AV1 in the next,
     * and a product serves both, so a client that can negotiate either always has a
     * working path.

planecontrol/current/com/rdk/hal/planecontrol/capture/ICapture.aidl:136

  • ICapture.open() documentation says the client selects the session format via ICaptureController.setProperty(), but ICaptureController only exposes setFormat(). This is inconsistent with the actual API surface.
     * The client selects the session's format through
     * `ICaptureController.setProperty()` in the `READY` state, before calling
     * `ICaptureController.start()` - the frame format and size it wants, and the depth
     * of the pool that holds them.

planecontrol/current/docs/capture/capture_interface.md:234

  • This paragraph refers to ICaptureController.setProperty(), but the capture controller API uses setFormat() and plane size is set via IPlaneControl.setProperty(). The current wording doesn't match the implemented AIDL surface.
A format, modifier or frame size outside `CaptureCapabilities` fails at `ICaptureController.setProperty()`, while it is still a configuration error rather than a stream of wrong pixels. A pool the platform's video memory region cannot satisfy fails at `ICaptureController.start()` with `CaptureErrorCode.OUT_OF_MEMORY`, rather than being silently trimmed, and a mapped source decoding a codec outside `supportedCodecs` fails there with `CaptureErrorCode.CODEC_NOT_CAPTURABLE`. None of them falls back to plane output.

planecontrol/current/com/rdk/hal/planecontrol/capture/CaptureErrorCode.aidl:70

  • CaptureErrorCode.FORMAT_UNSUPPORTED references CaptureCapabilities.supportedFourCCs / supportedModifiers, but CaptureCapabilities defines supportedFormats (paired FormatLayout[]). The @see is currently pointing at non-existent fields.
     * @see CaptureCapabilities.supportedFourCCs, CaptureCapabilities.supportedModifiers
     */

planecontrol/current/com/rdk/hal/planecontrol/capture/ICaptureControllerListener.aidl:44

  • The onPoolReady() comment has a broken sentence ("which is what as many buffers...") that obscures the intent. Tightening this wording will make the pool-depth contract clearer.
     * The array length is the number of buffers the vendor reserved, which is what
     * as many buffers as the platform calibrated for the throughput it can sustain. The length of this array IS the pool depth - it is not declared anywhere else, because there is nothing for a client to decide before it and nothing to check it against. Where the
     * session left it unset.

planecontrol/current/hfp-planecontrol.yaml:117

  • maxBufferCount refers to CaptureProperty.BUFFER_COUNT, but this PR's capture API doesn't define CaptureProperty and pool depth is described elsewhere as vendor-calibrated (reported via onPoolReady()). Keeping this key/comment is likely to confuse product profiles.

This issue also appears on line 120 of the same file.

          maxBufferCount: 8  # Maximum pool buffers (CaptureProperty.BUFFER_COUNT)

planecontrol/current/hfp-planecontrol.yaml:126

  • These comments reference CaptureProperty.WIDTH / CaptureProperty.HEIGHT, but capture frame size is controlled via the plane's Property.WIDTH / Property.HEIGHT (and there is no CaptureProperty type in this PR).

This issue also appears on line 131 of the same file.

          maxFrameWidth: 1920   # Maximum captured frame width (CaptureProperty.WIDTH)
          maxFrameHeight: 1080  # Maximum captured frame height (CaptureProperty.HEIGHT)

planecontrol/current/docs/capture/capture_interface.md:79

  • This requirement states both Codec.H264_AVC and Codec.AV1 are mandatory for every capture plane, but the capture plane HFP in this PR only declares H.264. Please align the written requirements with the declared product profile (or vice versa) so implementers and validators have one consistent baseline.
| **HAL.PLANECONTROL.CAPTURE.2** | Shall declare in `CaptureCapabilities.supportedCodecs` the codecs whose decoded frames a capture plane can deliver, and shall include `Codec.H264_AVC` and `Codec.AV1`.| Certification asks for H.264 in one cycle and AV1 in the next; a product serves both. A decoder opened for any other codec still decodes and displays normally. |

planecontrol/current/docs/capture/capture_interface.md:302

  • This example comment says both H264_AVC and AV1 are required, but the product profile in this PR only declares H264_AVC for capture. The example should match the stated baseline to avoid confusion.
// captureCapabilities.supportedCodecs   - H264_AVC and AV1 both required

planecontrol/current/docs/capture/capture_interface.md:39

  • This states both Codec.H264_AVC and Codec.AV1 are required on every capture plane, but the capture plane HFP in this PR only declares H.264. Align the narrative description with the baseline requirement so the documentation and profiles don't contradict each other.
`Codec.H264_AVC` and `Codec.AV1` are both required on every capture plane; certification asks for H.264 in one cycle and AV1 in the next, and a product serves both. A decoder opened for a codec outside `supportedCodecs` decodes and displays normally — it just cannot feed a capture plane, and `start()` fails with `CaptureErrorCode.CODEC_NOT_CAPTURABLE` if one is mapped to it.

… mandates nothing (#753)

supportedCodecs and supportedFormats state what a product can deliver.
Neither carries a required value: a platform that cannot capture a codec
or a format does not list it, and a mandate it cannot meet would not
change that.

setFormat() is therefore required before start(), which fails with
INVALID_CONFIGURATION when no pair was selected - there is no default
pair left to assume.

The HFP is brought to the same shape: supportedFormats replaces the two
independent lists, maxBufferCount goes with the field it named, and the
capture plane declares routing rather than display geometry.
Copilot AI review requested due to automatic review settings August 15, 2026 18:30

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 29 out of 29 changed files in this pull request and generated 1 comment.

Suppressed comments (4)

planecontrol/current/com/rdk/hal/planecontrol/IPlaneControl.aidl:265

  • The docstring says this is for a "video plane resource", but the API is specifically for planes of type PlaneType.CAPTURE. This wording is misleading for API consumers.
     * Gets a Capture interface for a video plane resource.

planecontrol/current/com/rdk/hal/planecontrol/capture/ICaptureControllerListener.aidl:44

  • The onPoolReady() documentation has a malformed sentence about pool depth ("which is what as many buffers...") that is hard to understand and appears to have been accidentally garbled. Clarifying this text will make the contract easier to follow.
     * The array length is the number of buffers the vendor reserved, which is what
     * as many buffers as the platform calibrated for the throughput it can sustain. The length of this array IS the pool depth - it is not declared anywhere else, because there is nothing for a client to decide before it and nothing to check it against. Where the
     * session left it unset.

planecontrol/current/com/rdk/hal/planecontrol/capture/ICapture.aidl:136

  • ICapture.open() documentation references ICaptureController.setProperty() and configuring pool depth, but the controller interface exposes setFormat() (and pool depth is not configured via the API). This is misleading and contradicts the actual AIDL surface.
     * The client selects the session's format through
     * `ICaptureController.setProperty()` in the `READY` state, before calling
     * `ICaptureController.start()` - the frame format and size it wants, and the depth
     * of the pool that holds them.

planecontrol/current/com/rdk/hal/planecontrol/capture/VideoBufferView.aidl:60

  • VideoBufferView.bufferIndex docs say that an index that names no buffer is ignored, but ICaptureController.releaseFrame() / acquireLatestFrame() explicitly treat out-of-range indices as EX_ILLEGAL_ARGUMENT. The documentation should match the API contract to avoid clients depending on undefined behaviour.
     * An index that names no buffer in the current pool is ignored, which is what
     * makes a release arriving after a stop safe.

Comment on lines +27 to +30
import com.rdk.hal.planecontrol.graphics.IGraphicsFbProvider;
import com.rdk.hal.planecontrol.graphics.IGraphicsFbProviderListener;
import com.rdk.hal.planecontrol.capture.ICapture;
import com.rdk.hal.planecontrol.capture.ICaptureEventListener;
@Ulrond

Ulrond commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator Author

Update — the interface has moved a long way since the last review

@ramkumarpattabiraman this is worth a fresh read rather than a diff; the PR description is rewritten to match.

Capture and graphics are now subpackages. com.rdk.hal.planecontrol.capture and .graphics, leaving com.rdk.hal.planecontrol for planes themselves — 9 files instead of 24, and a doc per package. Capture has never shipped (zero capture files in the frozen 0.1.0.0 and 0.2.0.0), so the package move costs no client anything.

Capture is reduced to a control surface. CaptureProperty and CapturePropertyKVPair are deleted. One call replaces them:

boolean setFormat(in FormatLayout format);   // one row of supportedFormats

FormatLayout pairs a fourcc with a modifier valid for that fourcc. Two independent lists implied the full cross-product was selectable, most of which no plane can deliver — a client would have found out at start(). Passing a declared pair back makes an invalid selection inexpressible rather than merely rejected.

Nothing is mandated any more. supportedCodecs and supportedFormats state what a product can deliver, and no value is required in either. A platform that cannot capture a codec or a format does not list it, and a mandate it cannot meet would not change that. setFormat() is consequently required before start().

Pool depth is the platform's. maxBufferCount is gone. The vendor calibrates depth from the throughput its decode and memory path sustains; the client learns it by counting what onPoolReady() delivers, which is where the number already was.

State is five values, not eight. UNKNOWN, OPENING and CLOSING had no observable moment — open() and close() return synchronously, so nothing could ever see them. CLOSED is now zero, so a default-constructed value reads as something true. STARTING earns its place: start() returns before the pool exists, and onPoolReady() is what moves it to STARTED.

Two things the interface never said, both of which a client would have hit:

  • The listeners are oneway, so onPoolReady() and onGraphicsFbReleased() arrive on a binder thread with no GL context current. An import must be handed to the thread that owns the context. VideoBufferView also holds ParcelFileDescriptor, which is move-only and delivered by const reference — a client duplicates the descriptors it intends to import from.
  • Descriptor lifetime across teardown. The client's descriptors are duplicated across binder and an imported image takes a further reference, so the memory outlives stop(). What stop() ends is the content guarantee, not the memory. No copy is needed for safety — only to keep a frame beyond the period the buffer is held.

Both docs now carry a worked example, and the examples are compiled. A scratch harness extracts the fenced C++ from the docs and builds it against the generated headers. It has caught three real defects so far: onPoolReady declared void where the backend generates Status, PropertyValue::make<> used on the parcelable rather than its nested union, and pool = buffers which cannot compile at all against a move-only ParcelFileDescriptor.

Requirements are renumbered per package — HAL.PLANECONTROL.1–6, …CAPTURE.1–11, …GRAPHICS.1–3 — and videodecoder is untouched by this PR.

@Ulrond

Ulrond commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator Author

Two vendor questions this interface depends on

Neither blocks review of the shape, but both decide whether it can be implemented as written.

1. Broadcom can do (fd, offset) — SDK26 checked

The per-SoC analysis behind this work concluded Broadcom's capture path exposes no Dma-Buf at all, based on Nexus 18.3 (plane 0 fd = -1, a CPU-mapped NEXUS_Surface_Lock pointer, RGBA downscale). That verdict is out of date. URSR SDK26 has:

/* fd is cached until NEXUS_Surface_Destroy */
NEXUS_Error NEXUS_Surface_CreateDescriptor_driver(
    NEXUS_SurfaceHandle surface,
    int *fd            /* GEM export from pixelMemory */ );

typedef struct NEXUS_SurfaceMemoryProperties {
    NEXUS_MemoryBlockHandle pixelMemory;   /* block used for the pixel buffer */
    unsigned pixelMemoryOffset;            /* offset from the start of pixelMemory */
    ...

NEXUS_Error NEXUS_Platform_CreateMemoryBlockDescriptor_driver(
    NEXUS_MemoryBlockHandle block,
    unsigned blockOffset,   /* must be 4kb aligned */
    unsigned size,          /* must be 4kb aligned */
    int *fd );

Three things follow:

  • (fd, offset) per plane is the right shape for Broadcom, not a problem for it. Several surfaces can share one pixelMemory at different offsets.
  • Offsets must be carried, never computed. pixelMemoryOffset is whatever the allocator chose and block descriptors are 4 KB aligned, so a client deriving the chroma start from stride × height would miss.
  • "Export once, keep for the session" is supported"fd is cached until NEXUS_Surface_Destroy".

Both APIs are marked "API is subject to change" and are attr{local=true}, so same-process only. Whether the vendor HAL sits where it can call them is an integration question worth confirming.

The interface already spans both shapes — AML/V4L2 gives per-plane fds at offset 0, Broadcom gives shared memory at non-zero offsets — so no change follows from this. It removes a doubt rather than creating work.

2. Where does AV1 film grain sit relative to the capture tap?

DPI-9 makes Film Grain Synthesis mandatory for AV1. Grain is synthesised after decode and must not enter reference frames, so hardware typically applies it in the display pipeline, downstream of the decoded picture buffer.

A capture plane taps decoded frames. So:

  • tap before grain — the client's texture is clean while the display shows grain; the captured picture differs visibly from the displayed one
  • tap after grain — matches the display, but synthesis has to run into the capture pool as well, which is silicon cost that may not exist

HAL.PLANECONTROL.CAPTURE.6 says frames are delivered applying no scaling, rotation, crop, colour conversion, tone-mapping or gamma adjustment. Grain is not in that list and arguably should not be — it is normative decode output per the AV1 spec, not a post-process the HAL adds. So the requirement does not answer it either way, and nothing else in planecontrol or videodecoder mentions grain.

This needs a vendor answer before AV1 capture is specified, and it sits alongside the AV1 decode-to-texture question already open against every SoC for 2026.1.

Copilot AI review requested due to automatic review settings August 15, 2026 18:50

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 29 out of 29 changed files in this pull request and generated no new comments.

Suppressed comments (7)

planecontrol/current/com/rdk/hal/planecontrol/capture/ICapture.aidl:136

  • The open() documentation references ICaptureController.setProperty(), but the controller interface exposes setFormat() (and frame size is configured via IPlaneControl properties). This is misleading for API consumers and should be updated to match the actual contract.
     * The client selects the session's format through
     * `ICaptureController.setProperty()` in the `READY` state, before calling
     * `ICaptureController.start()` - the frame format and size it wants, and the depth
     * of the pool that holds them.

planecontrol/current/docs/capture/capture_interface.md:289

  • This paragraph refers to ICaptureController.setProperty(), but the capture controller API uses setFormat(). Frame size is set via IPlaneControl.setProperty() on Property.WIDTH/HEIGHT. Update the text to reflect the actual API surface.
A format, modifier or frame size outside `CaptureCapabilities` fails at `ICaptureController.setProperty()`, while it is still a configuration error rather than a stream of wrong pixels. A pool the platform's video memory region cannot satisfy fails at `ICaptureController.start()` with `CaptureErrorCode.OUT_OF_MEMORY`, rather than being silently trimmed, and a mapped source decoding a codec outside `supportedCodecs` fails there with `CaptureErrorCode.CODEC_NOT_CAPTURABLE`. None of them falls back to plane output.

planecontrol/current/com/rdk/hal/planecontrol/capture/ICaptureControllerListener.aidl:44

  • The onPoolReady() comment has a broken sentence ("which is what as many buffers as...") that makes the pool-depth contract hard to understand. Please rewrite for clarity.
     * The array length is the number of buffers the vendor reserved, which is what
     * as many buffers as the platform calibrated for the throughput it can sustain. The length of this array IS the pool depth - it is not declared anywhere else, because there is nothing for a client to decide before it and nothing to check it against. Where the
     * session left it unset.

videodecoder/current/docs/video_decoder.md:242

  • In the output-routing table, the capture-plane row implies capture is selected only by getCapture(), but capture also depends on mapping the decoder’s video sink to a CAPTURE plane via IPlaneControl.setVideoSourceDestinationPlaneMapping(). Clarifying this avoids suggesting getCapture() alone changes decoder routing.
| **A capture plane** | The client opens a capture session against this decoder through `IPlaneControl.getCapture()`. | No. Frames are consumed through the capture plane. |

planecontrol/current/docs/capture/capture_interface.md:134

  • There is a stray comma/period in the requirement text (", .") which reads as a typo.
| **HAL.PLANECONTROL.CAPTURE.2** | Shall declare in `CaptureCapabilities.supportedCodecs` the codecs whose decoded frames a capture plane can deliver, .| The list is what the plane can capture, not what a product must offer. A decoder opened for a codec outside it decodes and displays normally; it just cannot feed this plane. A decoder opened for any other codec still decodes and displays normally. |

planecontrol/current/docs/capture/capture_interface.md:225

  • The docs say releaseFrame() “tolerates unknown indices” and that out-of-pool indices are ignored, but the AIDL contract for ICaptureController.releaseFrame() says out-of-pool indices raise EX_ILLEGAL_ARGUMENT. Please align the documentation with the interface contract (either update docs or update the AIDL contract).
Call `ICaptureController.releaseFrame(bufferIndex)` when the client stops drawing while still holding a buffer. A client drawing continuously has already released through the previous step. The call is idempotent and tolerates unknown indices.

Release is keyed by index because the index is the buffer's identity. An index that names no buffer in the current pool is ignored, which is what makes a release arriving after a stop safe.

planecontrol/current/com/rdk/hal/planecontrol/capture/VideoBufferView.aidl:65

  • This comment says out-of-pool buffer indices are ignored to make post-stop releases safe, but ICaptureController.releaseFrame() is documented as raising EX_ILLEGAL_ARGUMENT for indices outside the pool. Please align this description with the interface contract.
     * An index that names no buffer in the current pool is ignored, which is what
     * makes a release arriving after a stop safe.

@Ulrond

Ulrond commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator Author

An alternative to this PR is open as #782, against #781: the same capture interface as a module in its own right, com.rdk.hal.capture, rather than nested under planecontrol.

The AIDL types are the same in both — #782 carries them over verbatim with only the package rewritten. What differs is discovery and binding: #782 adds ICaptureManager and an ICapture.Id, and binds with ICapture.open(CaptureSource, listener) in place of setVideoSourceDestinationPlaneMapping().

This PR also changes planecontrol and videodecoder; #782 touches only capture/, leaving both byte-identical to develop. They are therefore reviewable as alternatives rather than as overlapping changes.

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

Labels

component:planecontrol SOC component: planecontrol component:videodecoder SOC component: videodecoder Major Change Breaking interface change (renames/removals/signature changes) — bumps major

Projects

Status: Under Review

Development

Successfully merging this pull request may close these issues.

Task: Extend planecontrol with a graphics capture interface (ICapture) — decoded-frame DMA-BUF capture for decode-to-texture

3 participants