Class FfmpegVideoDecoder

java.lang.Object
limn.video.ffmpeg.FfmpegVideoDecoder
All Implemented Interfaces:
VideoDecoder

public final class FfmpegVideoDecoder extends Object implements VideoDecoder
Decodes H.264, HEVC, VP9 and VP8 video and AAC, Opus and Vorbis audio out of MP4 and Matroska/WebM, through a trimmed FFmpeg behind a hand-written native shim.

AV1 is deliberately absent, and not because it was forgotten: FFmpeg's own AV1 decoder has no software path (it refuses outright unless a hardware accelerator is attached, and this build has none), so software AV1 means linking libdav1d, which is a second external library with its own build system and its own payload rather than another configure flag.

Install it the way an application installs any decoder (the order is the probe order, and nothing installs one on anybody's behalf):


 Videos.installDecoder(new FfmpegVideoDecoder());
 Videos.warmUpAsync().start();   // optional: links the native on a worker rather than on a click
 

Installing it with no native library is harmless and is an expected case. The FFmpeg libraries ride in a natives-<os>-<arch> classifier of the limn-ffmpeg-natives artifact an application adds for its platform (ADR 037), so a build that added none — or added another platform's — has no library here. This decoder then answers false to every input, the decoders behind it in the probe order are reached exactly as if it were not there, and unavailableReason() says why in one sentence for anything that wants to explain itself. Nothing throws, nothing logs, and nothing about the rest of the application changes.

This decoder returns video and only video, which is what the facade's entry point is shaped for. A container's soundtrack has nowhere to arrive through that shape, so an application that wants both opens FfmpegMedia directly and hands the two tracks to a player, which needs no change anywhere above this module, because they are the two types the toolkit already publishes.

Any thread. There is no state here beyond the decision of whether the library loaded, which is taken once per process.

  • Constructor Details

    • FfmpegVideoDecoder

      public FfmpegVideoDecoder()
  • Method Details

    • name

      public String name()
      Description copied from interface: VideoDecoder
      A short, stable, lower-case identifier used in diagnostics. It appears in the failure raised when nothing accepts an input, which is the only way anyone finds out which decoders existed and in what order they were consulted.

      It carries no meaning and nothing may branch on it; the wrong edit this prevents is a comparison against a literal name somewhere on a control-flow path, which would put a particular decoder's identity into logic that is supposed to be decoder-neutral.

      Deliberately without a default: deriving it from the implementing class produces an unreadable synthetic name for a lambda or an anonymous class, in exactly the message that exists to be readable.

      Specified by:
      name in interface VideoDecoder
      Returns:
      the identifier, never null and never blank
    • supports

      public boolean supports(Path file)
      Whether this decoder will attempt file.

      Cheap and honest. It may look at the extension and read the first few bytes; it must not parse a whole container, build an index, touch a network or decode anything, because it runs on the caller's thread, once per installed decoder, every time an input is opened.

      It promises only that this decoder claims the input, not that the input opens, is well-formed, or decodes to the end. It must never throw: a missing, unreadable or surprising input is false, because one bad file that throws here makes the whole probe unusable for every other decoder behind it.

      Deliberately without a default, because both possible defaults are wrong: claiming everything destroys the ordering, and claiming nothing makes a decoder that forgot to override it silently unreachable.

      Cheap, and cheap in the way the contract means: an extension comparison and twelve bytes read from the front of the file. It does not open a container, does not build an index and above all does not call avformat_find_stream_info; that call reads and decodes real data to fill in what an MP4 header does not state, which makes it exactly the wrong thing to run once per installed decoder every time anything at all is opened. It belongs in openStream(java.nio.file.Path), and that is where it is.

      Never throws, for any input. A path that does not exist, cannot be read, is a directory, or is shorter than a header is false, because a decoder that threw here would make the whole probe unusable for every decoder behind it, over one bad file.

      Specified by:
      supports in interface VideoDecoder
    • openStream

      public VideoStreamSource openStream(Path file)
      Opens file for picture-by-picture decoding. The caller owns the returned source and closes it.

      Called only after VideoDecoder.supports(java.nio.file.Path) returned true for the same path. Throwing here is final (no other decoder is tried), so throw with a message that says what was wrong with the input, because that message is strictly better than the generic one a fallthrough would produce.

      Unlike VideoDecoder.supports(java.nio.file.Path), this is allowed to be slow, and normally is. Reading a container's headers, probing every stream in it to fill in what those headers do not state, building an index, opening a decoder, attaching a hardware device, and (the first time) loading a native library are all this method's, and together they are far longer than a frame. It runs on whatever thread called it, so a caller on the UI thread loses every frame until it returns. That is what VideoDecoder.openStream(Path, Progress) exists for: it is the overload the asynchronous facade calls, and the one to override when there is anything here worth abandoning early.

      The returned source owns the container, so closing it releases the decoder and the input. The audio track is not claimed on this path and its packets are discarded as they are met; FfmpegMedia.open(Path) is what takes both.

      Specified by:
      openStream in interface VideoDecoder
      Throws:
      FfmpegException - if the input is not a container this build can demultiplex, holds no video, uses a codec this build was not compiled with, or is malformed in a way that stops it being read at all
    • warmUp

      public void warmUp()
      Does whatever this decoder would otherwise do lazily on its first real call (load a native library, extract a payload, build a table) so that no caller pays for it by surprise.

      It exists because VideoDecoder.supports(java.nio.file.Path) must stay cheap and cannot honour that while also being the call that first links a library: a file chooser merely asking whether a clip is playable would then pay the whole cost, on whichever thread asked. Called on a worker thread, allowed to block, and idempotent: the second call costs nothing, since what it prepares is prepared once per process.

      It must not throw, and nothing is decided by its outcome. A decoder that cannot prepare itself is in exactly the state it would have been in had nobody warmed it, and says so at the next VideoDecoder.supports(java.nio.file.Path) the way it always did.

      The default does nothing, which is right for a decoder whose first call is already cheap.

      Links the native library, which is this decoder's entire first-call cost and is otherwise charged to whoever asks first, including supports(java.nio.file.Path), which is cheap in every other respect and runs once per installed decoder every time anything is opened or merely asked about. On a build carrying the libraries in its jar that first call digests them, copies tens of megabytes out to a cache directory, links them in dependency order and runs an identity probe; on a machine with no build it is one failed lookup. Either way it happens once per process, and doing it here means it happens where no frame is waiting on it.

      It does not touch a file and cannot prepare an open: nothing about a container is known before there is a container. Never throws, on any machine: a missing native is the expected case and stays a false from supports(java.nio.file.Path), not a failure of the warm-up.

      Specified by:
      warmUp in interface VideoDecoder
    • isAvailable

      public static boolean isAvailable()
      Returns:
      whether this decoder can open anything at all on this machine; that is, whether the native library loaded
    • unavailableReason

      public static String unavailableReason()
      Returns:
      one sentence naming the platform that was looked for and what went wrong (no build for this operating system, a build for another processor, a temporary directory that cannot be written to), or null when the library did load. For a status line or a log; nothing should branch on its text.