Class FramePool

java.lang.Object
limn.video.decode.FramePool
All Implemented Interfaces:
VideoFrame.Recycler

public final class FramePool extends Object implements VideoFrame.Recycler
A fixed set of pictures a decoder fills and hands out, and the free list that takes them back. Every decoder needs exactly this and none of it is codec-specific: the planes are allocated once, the geometry is configured once, and publishing a picture afterwards writes two primitives.

Built for one size and one layout. A stream whose resolution changes builds a new pool; that is a rare event and rebuilding is cheaper than making every steady-state picture pay for the possibility.

The planes are direct memory with rows aligned to four bytes, which is what a device upload wants and what lets a picture reach the GPU without an intermediate copy. Each plane holds exactly PixelFormat.minPlaneBytes(int, int, int, int): the last row ends at its last sample with no trailing padding, which is legal, is what a tight producer hands over, and is therefore the shape worth exercising rather than the roomier one.

Threading: acquire() belongs to the single thread that decodes. recycle(limn.video.VideoFrame) is called by VideoFrame.release() from whichever thread released, so it is lock-free and never waits; a decoder blocked for a free slot would otherwise stay blocked until it returned.

  • Field Details

    • MAX_SLOTS

      public static final int MAX_SLOTS
      Most slots a pool may have. The free list is the bits of one long, which is what makes handing a slot back a single compare-and-set; a video pool wants a handful of pictures in flight, not sixty-four, so the ceiling costs nothing real.
      See Also:
    • MAX_BYTES

      public static final long MAX_BYTES
      Most direct memory one pool may reserve, over all its slots: one gibibyte. A pool is built from a header nobody has checked yet, before a single sample is read, and it commits its pages on construction; without a ceiling, thirty bytes claiming a picture of 32768 by 32768 reserved 1.5 GiB per slot and took the process down with them. The number is what the largest picture anyone plays needs with room to spare: an 8K frame (7680 by 4320) is 50 MiB in 4:2:0 at 8 bits and 200 MiB in 4:4:4 at 10 (two bytes a sample), so three of the latter fit.
      See Also:
  • Method Details

    • of

      public static FramePool of(int slots, int width, int height, PixelFormat format, VideoColor color)
      Allocates every picture this pool will ever hand out.
      Parameters:
      slots - pictures in flight at once, in [1..MAX_SLOTS]. One means the decoder stalls until the consumer releases; two lets a consumer hold the picture it is showing while the next is produced, which is the smallest useful number.
      width - picture width in pixels, in [1..PixelFormat.MAX_DIMENSION]
      height - picture height in pixels, in the same range
      Throws:
      IllegalArgumentException - if slots or a dimension is out of range, or the pool would reserve more than MAX_BYTES
      NullPointerException - if format or color is null
      OutOfMemoryError - if the direct memory for the planes cannot be reserved
    • bytesFor

      public static long bytesFor(int slots, int width, int height, PixelFormat format)
      The direct memory of(int, int, int, limn.video.PixelFormat, limn.video.VideoColor) would reserve for these arguments, computed without reserving any of it: what a source that has just read a header, and nothing else, asks before it commits to the header's word.
      Throws:
      IllegalArgumentException - if slots or a dimension is out of range
      NullPointerException - if format is null
    • slots

      public int slots()
      Returns:
      pictures this pool owns, free or not
    • format

      public PixelFormat format()
      Returns:
      the layout every picture from this pool uses
    • color

      public VideoColor color()
      Returns:
      the interpretation every picture from this pool carries
    • width

      public int width()
      Returns:
      picture width in pixels
    • height

      public int height()
      Returns:
      picture height in pixels
    • stride

      public int stride(int plane)
      Distance in bytes between the starts of consecutive rows of plane: the plane's byte width rounded up to four, not its sample count.
      Throws:
      IndexOutOfBoundsException - if plane is not a plane of format()
    • acquire

      public VideoFrame.Writer acquire()
      A slot to fill, or null when every picture is still held by a consumer, which a source reports as VideoStreamSource.Read.PENDING rather than as an end or an error. Cheap and non-blocking either way: it never waits for a consumer, so a caller that gets null must return to its own loop rather than spin here.

      Decode thread only. The returned writer is the pool's and is valid until the picture it publishes is released, at which point the same writer becomes acquirable again.

      Returns:
      a writer whose planes are the caller's to fill, or null
    • planeOf

      public ByteBuffer planeOf(int slot, int plane)
      The writable memory of one plane of one slot: the buffer the caller fills before publishing. The picture's own VideoFrame.plane(int) is a read-only view of this same memory, so writing here after publishing changes a picture a consumer is already looking at.

      Position and limit are the caller's to move: they are not shared with the picture's view, which VideoFrame.Writer.publish() rewinds on its own.

      Throws:
      IndexOutOfBoundsException - if slot or plane is not one of this pool's
    • recycle

      public void recycle(VideoFrame frame)
      Takes a released picture's slot back. Any thread, lock-free, and never called by a decoder directly; VideoFrame.release() calls it, exactly once per delivered picture.
      Specified by:
      recycle in interface VideoFrame.Recycler
      Parameters:
      frame - the frame whose slot came free; never null
      Throws:
      IllegalArgumentException - if the frame belongs to another pool, or its slot was already free. The second is the double-release that hands one slot to two producers, and the picture tearing it causes appears nowhere near the call that caused it.
    • abandon

      public void abandon(VideoFrame.Writer writer)
      Gives back a slot that was acquired and then not published: the decoder asked for somewhere to put a picture and found there was no picture to put there, which is what reaching the end of an input looks like from the inside. Without it the slot would be lost for the life of the pool, and a source that hit the end twice with a two-slot pool would stall forever afterwards.

      Decode thread only, and only for a writer this pool handed out and whose frame has not been published since.

      Throws:
      IllegalArgumentException - if the writer is not this pool's, or its slot is already free
      IllegalStateException - if the frame was published and is held by a consumer
    • freeSlots

      public int freeSlots()
      Returns:
      pictures currently free, for a diagnostic or a test; changes under any consumer