Class FfmpegVideoDecoder
- All Implemented Interfaces:
VideoDecoder
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 Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionstatic booleanname()A short, stable, lower-case identifier used in diagnostics.openStream(Path file) Opensfilefor picture-by-picture decoding.booleanWhether this decoder will attemptfile.static StringvoidwarmUp()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.Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, waitMethods inherited from interface limn.video.VideoDecoder
openStream
-
Constructor Details
-
FfmpegVideoDecoder
public FfmpegVideoDecoder()
-
-
Method Details
-
name
Description copied from interface:VideoDecoderA 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:
namein interfaceVideoDecoder- Returns:
- the identifier, never null and never blank
-
supports
Whether this decoder will attemptfile.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 inopenStream(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:
supportsin interfaceVideoDecoder
-
openStream
Opensfilefor 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 whatVideoDecoder.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:
openStreamin interfaceVideoDecoder- 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
falsefromsupports(java.nio.file.Path), not a failure of the warm-up.- Specified by:
warmUpin interfaceVideoDecoder
-
isAvailable
public static boolean isAvailable()- Returns:
- whether this decoder can open anything at all on this machine; that is, whether the native library loaded
-