Add MPS demuxer and FFmpeg H.264 video decode
- MpsDemuxer parses MPEG-PS packs (0x1BA) into video (0x1E0-0x1EF) and audio (0x1BD) PES packets with PTS/DTS at the 90kHz clock. - VideoDecoder decodes Annex-B H.264 via JavaCPP FFmpeg 8.1.2, splitting the stream into access units (one slice NAL per packet) so each send yields a frame. - CLI 'decode' command dumps frame metadata (720x480 YUVJ420P). - Validated against Batman Begins: 72 frames decoded.
This commit is contained in:
@@ -9,7 +9,6 @@ java {
|
||||
}
|
||||
|
||||
application {
|
||||
mainModule = "dev.quoije.umd.cli"
|
||||
mainClass = "dev.quoije.umd.cli.Main"
|
||||
}
|
||||
|
||||
|
||||
@@ -3,10 +3,12 @@ package dev.quoije.umd.cli;
|
||||
|
||||
import dev.quoije.umd.iso.UmdIsoFile;
|
||||
import dev.quoije.umd.iso.UmdIsoReader;
|
||||
import dev.quoije.umd.media.VideoDecoder;
|
||||
import dev.quoije.umd.mps.MpsDemuxer;
|
||||
import dev.quoije.umd.playlist.Playlist;
|
||||
import dev.quoije.umd.sfo.Sfo;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Path;
|
||||
@@ -15,7 +17,7 @@ import java.util.Map;
|
||||
/** Command-line front-end for the UMD player library. */
|
||||
public final class Main {
|
||||
|
||||
private static final String USAGE = "usage: umdplayer dump <image.iso>\n umdplayer mps <image.iso> <streamName>";
|
||||
private static final String USAGE = "usage: umdplayer dump <image.iso>\n umdplayer mps <image.iso> <streamName>\n umdplayer decode <image.iso> <streamName>";
|
||||
|
||||
private Main() {
|
||||
}
|
||||
@@ -31,6 +33,7 @@ public final class Main {
|
||||
exitCode = switch (args[0]) {
|
||||
case "dump" -> dump(requireArg(args, 1));
|
||||
case "mps" -> mps(requireArg(args, 1), requireArg(args, 2));
|
||||
case "decode" -> decode(requireArg(args, 1), requireArg(args, 2));
|
||||
case "help", "--help", "-h" -> {
|
||||
System.out.println(USAGE);
|
||||
yield 0;
|
||||
@@ -186,6 +189,52 @@ public final class Main {
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static int decode(String imagePath, String streamName) throws IOException {
|
||||
try (UmdIsoReader iso = new UmdIsoReader(Path.of(imagePath));
|
||||
MpsDemuxer demuxer = MpsDemuxer.open(iso, streamName)) {
|
||||
|
||||
ByteArrayOutputStream video = new ByteArrayOutputStream();
|
||||
int packets = 0;
|
||||
int videoPackets = 0;
|
||||
while (packets < 300) {
|
||||
MpsDemuxer.Packet pkt = demuxer.nextPacket();
|
||||
if (pkt == null) {
|
||||
break;
|
||||
}
|
||||
packets++;
|
||||
if (!pkt.isVideo()) {
|
||||
continue;
|
||||
}
|
||||
video.write(pkt.payload(), 0, pkt.payload().length);
|
||||
videoPackets++;
|
||||
}
|
||||
|
||||
byte[] annexB = video.toByteArray();
|
||||
|
||||
try (VideoDecoder decoder = new VideoDecoder()) {
|
||||
decoder.feedAnnexB(annexB);
|
||||
decoder.flush();
|
||||
|
||||
int frames = 0;
|
||||
VideoDecoder.Frame frame;
|
||||
while ((frame = decoder.receive()) != null) {
|
||||
frames++;
|
||||
if (frames <= 8) {
|
||||
long checksum = 0;
|
||||
for (byte b : frame.yPlane()) {
|
||||
checksum = checksum * 31 + (b & 0xFF);
|
||||
}
|
||||
System.out.printf("frame %d: %dx%d pixfmt=%d checksum=%d%n",
|
||||
frames, frame.width(), frame.height(), frame.pixelFormat(), checksum);
|
||||
}
|
||||
}
|
||||
System.out.printf("Decoded %d frames from %d video packets (%d total packets)%n",
|
||||
frames, videoPackets, packets);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static String hexPrefix(byte[] data, int max) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < Math.min(data.length, max); i++) {
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
module dev.quoije.umd.cli {
|
||||
requires dev.quoije.umd.core;
|
||||
}
|
||||
Reference in New Issue
Block a user