Package limn.video

Interface VideoStreamSource

All Superinterfaces:
AutoCloseable

public interface VideoStreamSource extends AutoCloseable
A pull source of decoded pictures: the video counterpart of an incrementally decoded audio stream, obtained from a VideoDecoder and driven one picture at a time.

A consumer cannot supply the destination: one 1080p picture in 4:2:0 is about 3.1 MB, and handing out a fresh one per picture is roughly 93 MB a second of garbage at 30 per second. Pictures are therefore owned by the source and pooled; readFrame() says what happened and frame() lends the result, which the consumer returns with VideoFrame.release(). Nothing on this path allocates.

Every metadata accessor (width(), height(), pixelFormat(), color(), rotationDegrees(), frameRateNum(), frameRateDen(), durationMicros(), canReset() and canSeek()) is fixed when the source is opened, never changes, and is readable from any thread. A source that cannot answer them before its first picture is decoded is not a valid implementation: a view has to be laid out before a picture exists.

Threading: after a player takes ownership, readFrame(), reset(), seek(long, SeekMode) and the final close() come from that player's single decode thread, never concurrently, serialized by the player. Implementations need no synchronization but must not assume any particular thread. The metadata accessors are the exception: they are read concurrently and must therefore be final state rather than lazily computed. readFrame() may block for a whole decode and must never be called on a thread that is drawing, and seek(long, SeekMode) may block for as long as a read does.

  • Field Details

  • Method Details

    • width

      int width()
      Returns:
      width in pixels of the luma plane
    • height

      int height()
      Returns:
      height in pixels of the luma plane
    • pixelFormat

      PixelFormat pixelFormat()
      Returns:
      the plane layout every picture from this source uses
    • color

      VideoColor color()
      Returns:
      how every picture from this source is to be interpreted; never null
    • rotationDegrees

      default int rotationDegrees()
      How far the picture must be turned clockwise to be displayed the right way up. Every recording made on a device that can be held sideways carries this, stored one way and meant to be seen another, and a consumer that ignores it shows a portrait recording on its side.

      The samples are not turned: width() and height() describe the picture as it is stored and every plane's geometry follows them, so at 90 or 270 the displayed width is height() and the displayed height is width(). A consumer that lays out a box for this stream swaps them; one that only uploads planes ignores this entirely.

      Returns:
      0, 90, 180 or 270, and nothing else. An implementation whose input describes a flip, a shear or an angle off the quarter turns reports 0 rather than the nearest right angle, because a picture silently shown mirrored is worse than one shown as stored.
    • frameRateNum

      int frameRateNum()
      Numerator of the nominal frame rate, kept as a rational so that rates like 30000/1001 are exact; a rate held as a fraction of a second drifts by a whole picture every few minutes. Nominal means what to expect, not what will arrive: a source whose pictures are unevenly spaced still reports its nominal rate here and puts the truth in each picture's timestamp.
      Returns:
      the numerator, or 0 when the rate is unknown
    • frameRateDen

      int frameRateDen()
      Returns:
      denominator of the nominal frame rate; never 0, so a caller may always divide
    • durationMicros

      default long durationMicros()
      Returns:
      total length in microseconds, or DURATION_UNKNOWN when the source cannot know it: a pipe, a live input, a container without a duration. Never an estimate presented as a fact.
    • readFrame

      Decodes the next picture.
      Returns:
      what happened; on VideoStreamSource.Read.FRAME the picture is in frame()
      Throws:
      RuntimeException - if the stream is malformed or the decode fails. A failed read is exceptional rather than a status, so the steady-state path carries no error object; a source that can skip a damaged picture skips it and returns the next one instead of throwing.
    • frame

      VideoFrame frame()
      The picture most recently produced by readFrame(), on loan. The source may not refill that slot until the consumer hands it back with VideoFrame.release(), so exactly one release per delivered picture is still the rule here.

      After VideoStreamSource.Read.END this keeps returning that same picture, because no further ones are produced. A player that wants the final image left on screen therefore simply does not release it until it is finished with it, which costs nothing: no slot the source still needs is being withheld.

      Returns:
      the borrowed picture, or null before the first VideoStreamSource.Read.FRAME and after close()
    • reset

      void reset()
      Rewinds to the first picture: how a player loops seamlessly, and the only repositioning this interface defines. Not a seek: no time, no accuracy mode, no failure short of the input being unable to rewind at all.
      Throws:
      UnsupportedOperationException - if canReset() is false
    • canReset

      default boolean canReset()
      Returns:
      whether reset() works. Asked before playback starts, so a player decides up front whether to offer looping rather than discovering it by catching an exception at the end of the input, which reaches the viewer as a stall.
    • seek

      default void seek(long micros, VideoStreamSource.SeekMode mode)
      Moves to micros so that the next readFrame() produces a picture there. Unlike reset() this is a position on the same timeline the pictures' presentation times are on, so seeking to a picture's own ptsMicros() in VideoStreamSource.SeekMode.EXACT produces that picture.

      What the next picture's timestamp is. In VideoStreamSource.SeekMode.EXACT it is the first at or after micros, under one picture interval late, never early. In VideoStreamSource.SeekMode.KEYFRAME it is at or before micros, by however much the input's own structure imposes, which for a source whose every picture is independently decodable is nothing at all. Neither mode promises a picture exactly at micros: a picture exists at the instants the producer put one, and asking for a time between two of them cannot conjure a third.

      Past the end is a position, not an error. A target beyond the last picture leaves the source at the end, and the next read reports VideoStreamSource.Read.END. A target at or below the first picture's time leaves the source at the beginning. Negative targets are the caller's mistake and are refused.

      What it costs. VideoStreamSource.SeekMode.KEYFRAME decodes nothing. VideoStreamSource.SeekMode.EXACT decodes and discards every picture between the nearest independently decodable one and the target, so it is bounded by the input's structure rather than by the distance travelled; a seek of one second can cost more than a seek of one minute.

      Pictures already lent out survive. This does not invalidate a picture the consumer is holding, and every one of them must still be released exactly once. A consumer that holds every pooled slot across a seek gets VideoStreamSource.Read.PENDING afterwards, exactly as it would without one.

      Called on the same thread as readFrame() and never concurrently with it.

      Parameters:
      micros - where to move to, in microseconds on the pictures' own timeline; not negative
      mode - how close to land, and therefore what this costs
      Throws:
      UnsupportedOperationException - if canSeek() is false
      IllegalArgumentException - if micros is negative
      RuntimeException - if the input could not be repositioned, which is a failure of the input rather than a property of the source and is therefore thrown rather than reported by canSeek()
    • canSeek

      default boolean canSeek()
      Returns:
      whether seek(long, SeekMode) works, which defaults to false so that a source written before seeking existed keeps telling the truth. Asked before a transport control is offered, so that a scrub bar a stream cannot honour is disabled rather than throwing under the viewer's finger. Independent of canReset(): an input that can be rewound to its start need not be able to reach the middle.
    • close

      void close()
      Releases decoder and input resources and invalidates every pooled picture. Idempotent. Afterwards frame() is null and readFrame() reports the end, so a cleanup block needs no ordering care. A picture a consumer kept across this call refers to storage that no longer exists; copy before closing, never after.
      Specified by:
      close in interface AutoCloseable