compressed:decodeing) {
+ try {
+ compressed.get();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ } catch (ExecutionException e) {
+ ExceptionTool.throwIOException(e);
+ }
+ }
+
+ tdb.mark("解码/拼接");
+ tdb.print();
+ return image;
+ }
+
+ @Override
+ public IIOMetadata getStreamMetadata() throws IOException {
+ return null; // 本实现不提供流元数据
+ }
+
+ @Override
+ public IIOMetadata getImageMetadata(int imageIndex) throws IOException {
+ return null; // 本实现不提供图像元数据
+ }
+
+ @Override
+ public int getNumImages(boolean allowSearch) throws IOException {
+ return 1; // KIF格式只包含单张图像(文件版本)
+ }
+
+ @Override
+ public void dispose() {
+
+ super.dispose();
+ }
+}
\ No newline at end of file
diff --git a/src/org/kne/codec/kif/KIFImageReaderSpi.java b/src/org/kne/codec/kif/KIFImageReaderSpi.java
new file mode 100644
index 0000000..a08d8b7
--- /dev/null
+++ b/src/org/kne/codec/kif/KIFImageReaderSpi.java
@@ -0,0 +1,130 @@
+package org.kne.codec.kif;
+
+import javax.imageio.ImageIO;
+import javax.imageio.ImageReader;
+import javax.imageio.spi.IIORegistry;
+import javax.imageio.spi.ImageReaderSpi;
+import javax.imageio.stream.ImageInputStream;
+import java.io.IOException;
+import java.util.Locale;
+
+/**
+ * KIF格式的ImageIO服务提供者。
+ * 这个类负责告诉ImageIO:我能处理.kif文件,并且我能创建对应的ImageReader。
+ */
+public class KIFImageReaderSpi extends ImageReaderSpi {
+
+ // 格式标识信息
+ private static final String VENDOR_NAME = "KIFCodec Project";
+ private static final String VERSION = "1.0";
+ private static final String[] FORMAT_NAMES = {"kif", "KIF"};
+ private static final String[] SUFFIXES = {"kif"};
+ private static final String[] MIME_TYPES = {"image/kif"};
+ private static final String READER_CLASS_NAME = KIFImageReader.class.getName();
+
+ // 标记是否支持流式读取(不支持,因为需要随机访问)
+ private static final boolean SUPPORTS_STANDARD_STREAM_METADATA = false;
+ private static final boolean SUPPORTS_STANDARD_IMAGE_METADATA = false;
+
+ // 单例实例,避免重复注册
+ private static KIFImageReaderSpi instance;
+
+ public KIFImageReaderSpi() {
+ super(
+ VENDOR_NAME, // vendorName
+ VERSION, // version
+ FORMAT_NAMES, // names
+ SUFFIXES, // suffixes
+ MIME_TYPES, // MIMETypes
+ READER_CLASS_NAME, // readerClassName
+ new Class>[]{ImageInputStream.class}, // inputTypes
+ null, // writerSpiNames
+ SUPPORTS_STANDARD_STREAM_METADATA,
+ null, // nativeStreamMetadataFormatName
+ null, // nativeStreamMetadataFormatClassName
+ null, // extraStreamMetadataFormatNames
+ null, // extraStreamMetadataFormatClassNames
+ SUPPORTS_STANDARD_IMAGE_METADATA,
+ null, // nativeImageMetadataFormatName
+ null, // nativeImageMetadataFormatClassName
+ null, // extraImageMetadataFormatNames
+ null // extraImageMetadataFormatClassNames
+ );
+ }
+
+ /**
+ * 手动注册此插件到 ImageIO 的全局注册表。
+ * 调用后,ImageIO 就能识别 .kif 格式。
+ *
+ * 使用示例:
+ *
+ * // 在应用启动时调用一次
+ * KIFImageReaderSpi.register();
+ *
+ * // 之后就可以正常使用 ImageIO 了
+ * BufferedImage img = ImageIO.read(new File("test.kif"));
+ *
+ *
+ * @return true 如果注册成功,false 如果已经注册过
+ */
+ public static void register() {
+ IIORegistry registry = IIORegistry.getDefaultInstance();
+
+ // 创建实例并注册
+ if (instance == null) {
+ instance = new KIFImageReaderSpi();
+ registry.registerServiceProvider(instance);
+ }
+ }
+
+ /**
+ * 从 ImageIO 的全局注册表中注销此插件。
+ *
+ */
+ public static void unregister() {
+ IIORegistry registry = IIORegistry.getDefaultInstance();
+
+ if (instance != null) {
+ registry.deregisterServiceProvider(instance);
+
+ }
+ }
+
+
+
+ @Override
+ public boolean canDecodeInput(Object source) throws IOException {
+ if (!(source instanceof ImageInputStream)) {
+ return false;
+ }
+
+ ImageInputStream stream = (ImageInputStream) source;
+ // 保存当前位置
+ long pos = stream.getStreamPosition();
+ try {
+ // 读取文件头魔数(Magic Number)
+ byte[] magic = new byte[3];
+ stream.readFully(magic);
+
+ // 检查是否为 "KIF" (0x4B 0x49 0x46)
+ if (magic[0] == 0x4B && magic[1] == 0x49 && magic[2] == 0x46) {
+ return true;
+ }
+
+ return false;
+ } finally {
+ // 恢复位置
+ stream.seek(pos);
+ }
+ }
+
+ @Override
+ public ImageReader createReaderInstance(Object extension) throws IOException {
+ return new KIFImageReader(this);
+ }
+
+ @Override
+ public String getDescription(Locale locale) {
+ return "KIF (KIFCodec Image Format) Image Reader";
+ }
+}
\ No newline at end of file
diff --git a/src/org/kne/codec/kif/KIFImageWriter.java b/src/org/kne/codec/kif/KIFImageWriter.java
new file mode 100644
index 0000000..72704b6
--- /dev/null
+++ b/src/org/kne/codec/kif/KIFImageWriter.java
@@ -0,0 +1,231 @@
+package org.kne.codec.kif;
+
+import javax.imageio.IIOImage;
+import javax.imageio.ImageTypeSpecifier;
+import javax.imageio.ImageWriteParam;
+import javax.imageio.ImageWriter;
+import javax.imageio.metadata.IIOMetadata;
+import javax.imageio.spi.ImageWriterSpi;
+import javax.imageio.stream.ImageOutputStream;
+
+import org.kne.debug.TimeDebugger;
+
+import java.awt.AlphaComposite;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Image;
+import java.awt.image.BufferedImage;
+import java.awt.image.DataBufferInt;
+import java.awt.image.RenderedImage;
+import java.io.IOException;
+import java.lang.foreign.MemorySegment;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.function.Supplier;
+import java.util.zip.Deflater;
+
+/**
+ * KIF格式的实际编码器。
+ * 这个类负责将BufferedImage编码为.kif文件。
+ */
+public class KIFImageWriter extends ImageWriter {
+ private static final int BLOCK_SIZE=512;
+ private ImageOutputStream outputStream;
+ private static ModeStatisticsCollector modeCollector=new ModeStatisticsCollector();
+
+ public static ModeStatisticsCollector getModeCollector() {
+ return modeCollector;
+ }
+
+ protected KIFImageWriter(ImageWriterSpi originatingProvider) {
+ super(originatingProvider);
+ }
+
+ @Override
+ public void setOutput(Object output) {
+ super.setOutput(output);
+ if (output instanceof ImageOutputStream) {
+ this.outputStream = (ImageOutputStream) output;
+ } else {
+ this.outputStream = null;
+ }
+ }
+
+ @Override
+ public IIOMetadata getDefaultStreamMetadata(ImageWriteParam param) {
+ return null;
+ }
+
+ @Override
+ public IIOMetadata getDefaultImageMetadata(ImageTypeSpecifier imageType, ImageWriteParam param) {
+ return null;
+ }
+
+ @Override
+ public IIOMetadata convertStreamMetadata(IIOMetadata inData, ImageWriteParam param) {
+ return null;
+ }
+
+ @Override
+ public IIOMetadata convertImageMetadata(IIOMetadata inData, ImageTypeSpecifier imageType, ImageWriteParam param) {
+ return null;
+ }
+
+ @Override
+ public void write(IIOMetadata streamMetadata, IIOImage image, ImageWriteParam param) throws IOException {
+ if (outputStream == null) {
+ throw new IllegalStateException("Output not set");
+ }
+
+ // 检查是否支持此图像类型
+ if (image.getRenderedImage() == null) {
+ throw new IllegalArgumentException("Image contains no RenderedImage");
+ }
+
+ RenderedImage renderedImage = image.getRenderedImage();
+ int width = renderedImage.getWidth();
+ int height = renderedImage.getHeight();
+
+
+ TimeDebugger tdb=new TimeDebugger("写入时间",false);
+ // 方法1:如果 RenderedImage 是 BufferedImage,可以直接操作
+ BufferedImage bi = null;
+ if (renderedImage instanceof BufferedImage) {
+ bi = (BufferedImage) renderedImage;
+ } else {
+ // 如果不是 BufferedImage,创建一个副本(简化处理)
+ bi = BufferedImageMembandBoost.createUninitializedBufferedImage(width, height) ;
+ Graphics2D g= (Graphics2D) bi.getGraphics();
+ g.setComposite(AlphaComposite.Src);
+ g .drawImage((Image) renderedImage, 0, 0, null);
+ }
+ /*
+ // 在 KIFImageWriter.write() 中
+ BufferedImage original = (BufferedImage) image.getRenderedImage();
+
+ // 统一转换为 TYPE_INT_ARGB
+ BufferedImage rgbImage =null;
+ if(original.getType()!=BufferedImage.TYPE_INT_ARGB) {
+ rgbImage = BufferedImageMembandBoost.createUninitializedBufferedImage( original.getWidth(), original.getHeight() );
+ Graphics2D g = rgbImage.createGraphics();
+ g.setComposite(AlphaComposite.Src);
+ g.drawImage(original, 0, 0, null);
+ g.dispose();
+ }else {
+ rgbImage=original;
+ }*/
+ tdb.mark("格式转换");
+ List>encoding=new ArrayList<>();
+ Listbcd=BlockCoord.calculateBlockCoords(width,height, BLOCK_SIZE, BLOCK_SIZE);
+ KIFCodec.backPressure();
+ for(BlockCoord crd:bcd) {
+ final BufferedImage bi2=bi;
+ encoding.add( KIFCodec.encodeAsyncMultimode(()->{
+ return IntImageBlock.createBlock(bi2,crd);
+ },KIFCompressMode.getNaturalImageModes()));
+ }
+ tdb.mark("分块提交");
+
+
+ // ===== 写入文件头 =====
+ // 魔数 (3 bytes) "KIF"
+ outputStream.write(0x4B); // 'K'
+ outputStream.write(0x49); // 'I'
+ outputStream.write(0x46); // 'F'
+
+ // 版本号 (1 byte)
+ outputStream.writeByte(1);
+
+ // 图像类型 (1 byte): 0=灰度, 1=RGB, 2=RGBA
+ int imageType = bi.getType();
+ // System.out.println("类型:"+imageType);
+ int typeFlag = 0;
+ if (imageType == BufferedImage.TYPE_INT_RGB ||
+ imageType == BufferedImage.TYPE_3BYTE_BGR) {
+ typeFlag = 1;
+ } else if (imageType == BufferedImage.TYPE_INT_ARGB ||
+ imageType == BufferedImage.TYPE_4BYTE_ABGR) {
+ typeFlag = 2;
+ }
+ outputStream.writeByte(typeFlag);
+
+ // 宽度 (4 bytes, 大端序)
+ outputStream.writeInt(width);
+
+ // 高度 (4 bytes, 大端序)
+ outputStream.writeInt(height);
+
+ // ===== 写入压缩的图像数据 =====
+
+ outputStream.writeInt(encoding.size());
+ tdb.mark("写入头");
+ while(true) {
+ for (Iterator> iterator = encoding.iterator(); iterator.hasNext();) {
+ Future v = iterator.next();
+ try {
+ if(v.isDone()) {
+ KIFCompressedImageBlock blk=v.get();
+ blk.writeToStream(outputStream);
+ modeCollector.addRecord(blk.getPredictMode());
+ iterator.remove();
+ }
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ } catch (ExecutionException e) {
+ ExceptionTool.throwIOException(e);
+ }
+ }
+ if(encoding.isEmpty()) {
+ break;
+ }
+ try {
+ Thread.sleep(1);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ tdb.mark("等待编码完成");
+ // 注意:写入完成后记得 flush
+ outputStream.flush();
+ tdb.mark("写入文件");
+ tdb.print();
+ }
+
+ /**
+ * 将 BufferedImage 转换为平面像素数组(逐行,每个通道独立)
+ * 这是一个辅助方法,实际实现时可根据需要调整
+ */
+ private byte[][] getPixelBands(BufferedImage image) {
+ int width = image.getWidth();
+ int height = image.getHeight();
+ int bands = image.getSampleModel().getNumBands();
+
+ byte[][] bandData = new byte[bands][width * height];
+
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < width; x++) {
+ int idx = y * width + x;
+ int rgb = image.getRGB(x, y);
+ // 根据图像类型提取各个通道
+ // 简化:假设为 RGB
+ if (bands >= 3) {
+ bandData[0][idx] = (byte) ((rgb >> 16) & 0xFF); // R
+ bandData[1][idx] = (byte) ((rgb >> 8) & 0xFF); // G
+ bandData[2][idx] = (byte) (rgb & 0xFF); // B
+ } else if (bands == 1) {
+ bandData[0][idx] = (byte) (rgb & 0xFF); // 灰度
+ }
+ }
+ }
+ return bandData;
+ }
+
+ @Override
+ public void dispose() {
+ super.dispose();
+ }
+}
\ No newline at end of file
diff --git a/src/org/kne/codec/kif/KIFImageWriterSpi.java b/src/org/kne/codec/kif/KIFImageWriterSpi.java
new file mode 100644
index 0000000..8b87fc5
--- /dev/null
+++ b/src/org/kne/codec/kif/KIFImageWriterSpi.java
@@ -0,0 +1,94 @@
+package org.kne.codec.kif;
+
+import javax.imageio.ImageTypeSpecifier;
+import javax.imageio.ImageWriter;
+import javax.imageio.spi.IIORegistry;
+import javax.imageio.spi.ImageWriterSpi;
+import javax.imageio.stream.ImageOutputStream;
+import java.awt.image.RenderedImage;
+import java.io.IOException;
+import java.util.Locale;
+
+/**
+ * KIF格式的ImageIO服务提供者(Writer端)。
+ * 这个类负责告诉ImageIO:我能写入.kif格式,并且我能创建对应的ImageWriter。
+ */
+public class KIFImageWriterSpi extends ImageWriterSpi {
+
+ private static final String VENDOR_NAME = "KIFCodec Project";
+ private static final String VERSION = "1.0";
+ private static final String[] FORMAT_NAMES = {"kif", "KIF"};
+ private static final String[] SUFFIXES = {"kif"};
+ private static final String[] MIME_TYPES = {"image/kif"};
+ private static final String WRITER_CLASS_NAME = KIFImageWriter.class.getName();
+
+ private static final boolean SUPPORTS_STANDARD_STREAM_METADATA = false;
+ private static final boolean SUPPORTS_STANDARD_IMAGE_METADATA = false;
+
+ // 单例
+ private static KIFImageWriterSpi instance;
+
+ public KIFImageWriterSpi() {
+ super(
+ VENDOR_NAME,
+ VERSION,
+ FORMAT_NAMES,
+ SUFFIXES,
+ MIME_TYPES,
+ WRITER_CLASS_NAME,
+ new Class>[]{ImageOutputStream.class},
+ new String[]{KIFImageReaderSpi.class.getName()}, // readerSpiNames
+ SUPPORTS_STANDARD_STREAM_METADATA,
+ null, // nativeStreamMetadataFormatName
+ null, // nativeStreamMetadataFormatClassName
+ null, // extraStreamMetadataFormatNames
+ null, // extraStreamMetadataFormatClassNames
+ SUPPORTS_STANDARD_IMAGE_METADATA,
+ null, // nativeImageMetadataFormatName
+ null, // nativeImageMetadataFormatClassName
+ null, // extraImageMetadataFormatNames
+ null // extraImageMetadataFormatClassNames
+ );
+ }
+
+ // ========== 手动注册方法 ==========
+
+ public static void register() {
+ IIORegistry registry = IIORegistry.getDefaultInstance();
+
+ if (instance == null) {
+ instance = new KIFImageWriterSpi();
+ registry.registerServiceProvider(instance);
+ }
+ }
+
+ public static void unregister() {
+ IIORegistry registry = IIORegistry.getDefaultInstance();
+ if (instance != null) {
+ registry.deregisterServiceProvider(instance);
+ instance=null;
+ }
+ }
+
+
+ // ========== SPI 必须实现的方法 ==========
+
+ @Override
+ public boolean canEncodeImage(ImageTypeSpecifier type) {
+ // 检查是否支持此图像类型
+ // 简化版:支持大部分常见类型
+ int sampleSize = type.getSampleModel().getSampleSize(0);
+ // 支持 8-bit 灰度或 RGB
+ return sampleSize == 8;
+ }
+
+ @Override
+ public ImageWriter createWriterInstance(Object extension) throws IOException {
+ return new KIFImageWriter(this);
+ }
+
+ @Override
+ public String getDescription(Locale locale) {
+ return "KIF (KIFCodec Image Format) Image Writer";
+ }
+}
\ No newline at end of file
diff --git a/src/org/kne/codec/kif/ModeStatisticsCollector.java b/src/org/kne/codec/kif/ModeStatisticsCollector.java
new file mode 100644
index 0000000..8ebf11f
--- /dev/null
+++ b/src/org/kne/codec/kif/ModeStatisticsCollector.java
@@ -0,0 +1,35 @@
+package org.kne.codec.kif;
+
+/**
+ * KIF 压缩模式统计收集器(线程安全)
+ * 专门统计 KIFCompressMode 的出现次数与概率
+ */
+public class ModeStatisticsCollector extends StatisticsCollector {
+
+ private static final ModeFormatter FORMATTER = new ModeFormatter();
+
+ /**
+ * 生成表格形式的统计报告
+ */
+ @Override
+ public String toString() {
+ return formatTable(FORMATTER);
+ }
+
+ /**
+ * 生成 CSV 格式报告
+ */
+ public String toCSV() {
+ return super.toCSV(FORMATTER);
+ }
+
+ /**
+ * KIFCompressMode 格式化器
+ */
+ private static class ModeFormatter implements ItemFormatter {
+ @Override
+ public String format(KIFCompressMode mode) {
+ return mode.toString();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/org/kne/codec/kif/MultiModeFuture.java b/src/org/kne/codec/kif/MultiModeFuture.java
new file mode 100644
index 0000000..f6e2bea
--- /dev/null
+++ b/src/org/kne/codec/kif/MultiModeFuture.java
@@ -0,0 +1,247 @@
+package org.kne.codec.kif;
+import java.util.ArrayList;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * 多模式并行编码的 Future 包装器
+ * 等待所有子任务完成,选择体积最小的结果
+ */
+public class MultiModeFuture implements Future {
+
+ private final ArrayList> futures;
+ private volatile KIFCompressedImageBlock bestResult;
+ private volatile boolean done = false;
+ private volatile boolean cancelled = false;
+ private final Object lock = new Object();
+
+ public MultiModeFuture(ArrayList< Future> futures) {
+ if (futures == null || futures.size() == 0) {
+ throw new IllegalArgumentException("至少需要1个子任务");
+ }
+ this.futures = futures;
+ }
+
+ @Override
+ public boolean cancel(boolean mayInterruptIfRunning) {
+ if (isDone()) {
+ return false;
+ }
+ cancelled = true;
+ boolean allCancelled = true;
+ for (Future f : futures) {
+ if (!f.cancel(mayInterruptIfRunning)) {
+ allCancelled = false;
+ }
+ }
+ return allCancelled;
+ }
+
+ @Override
+ public boolean isCancelled() {
+ return cancelled;
+ }
+
+ @Override
+ public boolean isDone() {
+ // 如果已经标记完成,直接返回 true
+ if (done) {
+ return true;
+ }
+
+ // 检查是否所有子任务都已完成
+ boolean allDone = true;
+ for (Future f : futures) {
+ if (!f.isDone()) {
+ allDone = false;
+ break;
+ }
+ }
+
+ if (allDone) {
+ // 所有子任务完成,自动合并结果(不阻塞)
+ synchronized (lock) {
+ if (!done) {
+ // 如果之前没有合并过,现在合并
+ KIFCompressedImageBlock minBlock = null;
+ Exception lastException = null;
+
+ for (Future f : futures) {
+ try {
+ KIFCompressedImageBlock block = f.get(); // 此时不会阻塞,因为 isDone() 已为 true
+ if (block != null) {
+ if (minBlock == null || block.compareTo(minBlock) < 0) {
+ minBlock = block;
+ }
+ }
+ } catch (Exception e) {
+ lastException = e;
+ }
+ }
+
+ if (minBlock != null||(lastException!=null)) {
+ bestResult = minBlock;
+ done = true;
+ }
+ }
+ }
+ }
+
+ return done;
+ }
+
+ @Override
+ public KIFCompressedImageBlock get() throws InterruptedException, ExecutionException {
+ if (done && bestResult != null) {
+ return bestResult;
+ }
+
+ synchronized (lock) {
+ if (done && bestResult != null) {
+ return bestResult;
+ }
+
+ // 等待所有子任务完成
+ Exception lastException = null;
+ KIFCompressedImageBlock minBlock = null;
+
+ for (Future f : futures) {
+ try {
+ KIFCompressedImageBlock block = f.get();
+ if (block != null) {
+ if (minBlock == null || block.compareTo(minBlock) < 0) {
+ minBlock = block;
+ }
+ }
+ } catch (ExecutionException e) {
+ lastException = e;
+ // 继续收集其他任务的结果
+ }
+ }
+
+ // 如果有至少一个成功的结果,返回最小的
+ if (minBlock != null) {
+ bestResult = minBlock;
+ done = true;
+ return bestResult;
+ }
+
+ // 全部失败,抛出最后一个异常
+ if (lastException != null) {
+ throw new ExecutionException(lastException);
+ }
+
+ // 理论上不会走到这里
+ throw new ExecutionException(new IllegalStateException("所有子任务返回了 null"));
+ }
+ }
+
+ @Override
+ public KIFCompressedImageBlock get(long timeout, TimeUnit unit)
+ throws InterruptedException, ExecutionException, TimeoutException {
+ if (done && bestResult != null) {
+ return bestResult;
+ }
+
+ synchronized (lock) {
+ if (done && bestResult != null) {
+ return bestResult;
+ }
+
+ long deadline = System.nanoTime() + unit.toNanos(timeout);
+ Exception lastException = null;
+ KIFCompressedImageBlock minBlock = null;
+
+ for (Future f : futures) {
+ long remaining = deadline - System.nanoTime();
+ if (remaining <= 0) {
+ throw new TimeoutException("等待超时");
+ }
+
+ try {
+ KIFCompressedImageBlock block = f.get(remaining, TimeUnit.NANOSECONDS);
+ if (block != null) {
+ if (minBlock == null || block.compareTo(minBlock) < 0) {
+ minBlock = block;
+ }
+ }
+ } catch (ExecutionException e) {
+ lastException = e;
+ }
+ }
+
+ if (minBlock != null) {
+ bestResult = minBlock;
+ done = true;
+ return bestResult;
+ }
+
+ if (lastException != null) {
+ throw new ExecutionException(lastException);
+ }
+
+ throw new ExecutionException(new IllegalStateException("所有子任务返回了 null"));
+ }
+ }
+ /* @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("MultiModeFuture{\n");
+
+ // 获取各个子任务的模式(如果能取到的话)
+ // 由于 Future 可能还没完成,我们只能尽量获取已完成的信息
+ sb.append(" 子任务数: ").append(futures.length).append("\n");
+
+ // 尝试获取每个子任务的结果(如果已完成)
+ for (int i = 0; i < futures.length; i++) {
+ Future f = futures[i];
+ if (f.isDone()) {
+ try {
+ // 如果已经完成,可以安全获取
+ KIFCompressedImageBlock block = f.get();
+ if (block != null) {
+ sb.append(String.format(" 模式 %d: size=%d bytes, 压缩率=%.2f%%\n",
+ block.getPredictMode() & 0xFF,
+ block.getData().length,
+ (double) block.getData().length / block.getSizeBeforeCompress() * 100
+ ));
+ } else {
+ sb.append(" 模式 ").append(i).append(": null\n");
+ }
+ } catch (Exception e) {
+ sb.append(" 模式 ").append(i).append(": 获取失败 (").append(e.getMessage()).append(")\n");
+ }
+ } else {
+ // 如果还没完成,尝试获取模式(需要额外传递模式信息)
+ // 这里无法直接从 Future 中提取模式,因为 Future 只存结果
+ // 可以在调用时传入模式编号或使用自定义 Future 子类携带模式信息
+ sb.append(" 子任务 ").append(i).append(": 未完成\n");
+ }
+ }
+
+ // 如果有最终结果,显示
+ if (done && bestResult != null) {
+ sb.append(" 最终选择:\n");
+ sb.append(String.format(" 模式: %d\n", bestResult.getPredictMode() & 0xFF));
+ sb.append(String.format(" 体积: %d bytes\n", bestResult.getData().length));
+ sb.append(String.format(" 压缩率: %.2f%%\n",
+ (double) bestResult.getData().length / bestResult.getSizeBeforeCompress() * 100
+ ));
+ sb.append(" 宽x高: ").append(bestResult.getWidth()).append("x").append(bestResult.getHeight());
+ } else if (done && bestResult == null) {
+ sb.append(" 最终结果: null (所有子任务失败)\n");
+ } else {
+ sb.append(" 状态: 未完成,仍在等待子任务...\n");
+ }
+
+ sb.append("}");
+ return sb.toString();
+ }*/
+ @Override
+ public String toString() {
+ return bestResult.toString();
+ }
+
+}
\ No newline at end of file
diff --git a/src/org/kne/codec/kif/PixelSerializer.java b/src/org/kne/codec/kif/PixelSerializer.java
new file mode 100644
index 0000000..439c0ff
--- /dev/null
+++ b/src/org/kne/codec/kif/PixelSerializer.java
@@ -0,0 +1,256 @@
+package org.kne.codec.kif;
+
+import java.awt.image.BufferedImage;
+import java.awt.image.DataBufferInt;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import org.kne.membandboost.MembandBoost;
+
+/**
+ * int[] 像素数组与 byte[] 的相互转换工具
+ * 每个像素按 R, G, B 三个字节依次写入
+ */
+public class PixelSerializer {
+
+ // ==================== 基础序列化/反序列化 (RGB, 3通道) ====================
+
+ public static byte[] serialize(int[] pixels, int w, int h) {
+ int len = pixels.length;
+ byte[] result = MembandBoost.allocateUninitializedByteArray(len * 3);
+ for (int i = 0; i < len; i++) {
+ int pixel = pixels[i];
+ int idx = i * 3;
+ result[idx] = (byte) ((pixel >> 16) & 0xFF);
+ result[idx + 1] = (byte) ((pixel >> 8) & 0xFF);
+ result[idx + 2] = (byte) (pixel & 0xFF);
+ }
+ return result;
+ }
+
+ public static int[] deserialize(byte[] data, int w, int h) throws IOException {
+ int expectedLen = w * h * 3;
+ if (data.length != expectedLen) {
+ throw new IOException("数据长度不匹配: 期望 " + expectedLen + " 字节, 实际 " + data.length + " 字节");
+ }
+ int[] pixels = MembandBoost.allocateUninitializedIntArray(w * h);
+ for (int i = 0; i < pixels.length; i++) {
+ int idx = i * 3;
+ int r = data[idx] & 0xFF;
+ int g = data[idx + 1] & 0xFF;
+ int b = data[idx + 2] & 0xFF;
+ pixels[i] = (r << 16) | (g << 8) | b;
+ }
+ return pixels;
+ }
+
+ // ==================== 合并 Zigzag (RGB, 3通道) ====================
+
+ public static byte[] serializeWithZigzag(int[] pixels, int w, int h) {
+ int len = pixels.length;
+ byte[] result = MembandBoost.allocateUninitializedByteArray(len * 3);
+ for (int i = 0; i < len; i++) {
+ int pixel = pixels[i];
+ byte r = (byte) ((pixel >> 16) & 0xFF);
+ byte g = (byte) ((pixel >> 8) & 0xFF);
+ byte b = (byte) (pixel & 0xFF);
+ byte mr = Zigzag.map(r);
+ byte mg = Zigzag.map(g);
+ byte mb = Zigzag.map(b);
+ int idx = i * 3;
+ result[idx] = mr;
+ result[idx + 1] = mg;
+ result[idx + 2] = mb;
+ }
+ return result;
+ }
+
+ public static int[] deserializeWithZigzag(byte[] data, int w, int h) throws IOException {
+ int expectedLen = w * h * 3;
+ if (data.length != expectedLen) {
+ throw new IOException("数据长度不匹配: 期望 " + expectedLen + " 字节, 实际 " + data.length + " 字节");
+ }
+ int[] pixels = MembandBoost.allocateUninitializedIntArray(w * h);
+ for (int i = 0; i < pixels.length; i++) {
+ int idx = i * 3;
+ byte r = Zigzag.unmap(data[idx]);
+ byte g = Zigzag.unmap(data[idx + 1]);
+ byte b = Zigzag.unmap(data[idx + 2]);
+ pixels[i] = ((r & 0xFF) << 16) | ((g & 0xFF) << 8) | (b & 0xFF);
+ }
+ return pixels;
+ }
+
+ // ==================== 平面序列化 (RGB, 3通道) ====================
+
+ public static byte[] serializePlannarWithZigzag(int[] pixels, int w, int h) {
+ int len = pixels.length;
+ byte[] result = MembandBoost.allocateUninitializedByteArray(len * 3);
+ int rOffset = 0;
+ int gOffset = len;
+ int bOffset = len * 2;
+ for (int i = 0; i < len; i++) {
+ int pixel = pixels[i];
+ byte r = (byte) ((pixel >> 16) & 0xFF);
+ byte g = (byte) ((pixel >> 8) & 0xFF);
+ byte b = (byte) (pixel & 0xFF);
+ result[rOffset + i] = Zigzag.map(r);
+ result[gOffset + i] = Zigzag.map(g);
+ result[bOffset + i] = Zigzag.map(b);
+ }
+ return result;
+ }
+
+ public static int[] deserializePlannarWithZigzag(byte[] data, int w, int h) throws IOException {
+ int expectedLen = w * h * 3;
+ if (data.length != expectedLen) {
+ throw new IOException("数据长度不匹配: 期望 " + expectedLen + " 字节, 实际 " + data.length + " 字节");
+ }
+ int len = w * h;
+ int[] pixels = MembandBoost.allocateUninitializedIntArray(len);
+ int rOffset = 0;
+ int gOffset = len;
+ int bOffset = len * 2;
+ for (int i = 0; i < len; i++) {
+ byte r = Zigzag.unmap(data[rOffset + i]);
+ byte g = Zigzag.unmap(data[gOffset + i]);
+ byte b = Zigzag.unmap(data[bOffset + i]);
+ pixels[i] = ((r & 0xFF) << 16) | ((g & 0xFF) << 8) | (b & 0xFF);
+ }
+ return pixels;
+ }
+
+ // ==================== RGBA 基础序列化 (4通道) ====================
+
+ /**
+ * RGBA 序列化:每个像素 [R, G, B, A] 四个字节(交错存储)
+ */
+ public static byte[] serializeRGBA(int[] pixels, int w, int h) {
+ int len = pixels.length;
+ byte[] result = MembandBoost.allocateUninitializedByteArray(len * 4);
+ for (int i = 0; i < len; i++) {
+ int pixel = pixels[i];
+ int idx = i * 4;
+ result[idx] = (byte) ((pixel >> 16) & 0xFF);
+ result[idx + 1] = (byte) ((pixel >> 8) & 0xFF);
+ result[idx + 2] = (byte) (pixel & 0xFF);
+ result[idx + 3] = (byte) ((pixel >> 24) & 0xFF);
+ }
+ return result;
+ }
+
+ /**
+ * RGBA 反序列化
+ */
+ public static int[] deserializeRGBA(byte[] data, int w, int h) throws IOException {
+ int expectedLen = w * h * 4;
+ if (data.length != expectedLen) {
+ throw new IOException("数据长度不匹配: 期望 " + expectedLen + " 字节, 实际 " + data.length + " 字节");
+ }
+ int[] pixels = MembandBoost.allocateUninitializedIntArray(w * h);
+ for (int i = 0; i < pixels.length; i++) {
+ int idx = i * 4;
+ int r = data[idx] & 0xFF;
+ int g = data[idx + 1] & 0xFF;
+ int b = data[idx + 2] & 0xFF;
+ int a = data[idx + 3] & 0xFF;
+ pixels[i] = (a << 24) | (r << 16) | (g << 8) | b;
+ }
+ return pixels;
+ }
+
+ // ==================== RGBA + Zigzag (交错) ====================
+
+ public static byte[] serializeRGBAWithZigzag(int[] pixels, int w, int h) {
+ int len = pixels.length;
+ byte[] result = MembandBoost.allocateUninitializedByteArray(len * 4);
+ for (int i = 0; i < len; i++) {
+ int pixel = pixels[i];
+ byte r = (byte) ((pixel >> 16) & 0xFF);
+ byte g = (byte) ((pixel >> 8) & 0xFF);
+ byte b = (byte) (pixel & 0xFF);
+ byte a = (byte) ((pixel >> 24) & 0xFF);
+ byte mr = Zigzag.map(r);
+ byte mg = Zigzag.map(g);
+ byte mb = Zigzag.map(b);
+ byte ma = Zigzag.map(a); // Alpha 也做 Zigzag
+ int idx = i * 4;
+ result[idx] = mr;
+ result[idx + 1] = mg;
+ result[idx + 2] = mb;
+ result[idx + 3] = ma;
+ }
+ return result;
+ }
+
+ public static int[] deserializeRGBAWithZigzag(byte[] data, int w, int h) throws IOException {
+ int expectedLen = w * h * 4;
+ if (data.length != expectedLen) {
+ throw new IOException("数据长度不匹配: 期望 " + expectedLen + " 字节, 实际 " + data.length + " 字节");
+ }
+ int[] pixels = MembandBoost.allocateUninitializedIntArray(w * h);
+ for (int i = 0; i < pixels.length; i++) {
+ int idx = i * 4;
+ byte r = Zigzag.unmap(data[idx]);
+ byte g = Zigzag.unmap(data[idx + 1]);
+ byte b = Zigzag.unmap(data[idx + 2]);
+ byte a = Zigzag.unmap(data[idx + 3]);
+ pixels[i] = ((a & 0xFF) << 24) | ((r & 0xFF) << 16) | ((g & 0xFF) << 8) | (b & 0xFF);
+ }
+ return pixels;
+ }
+
+ // ==================== RGBA 平面序列化 (RRRR...GGGG...BBBB...AAAA...) ====================
+
+ public static byte[] serializePlannarRGBAWithZigzag(int[] pixels, int w, int h) {
+ int len = pixels.length;
+ byte[] result = MembandBoost.allocateUninitializedByteArray(len * 4);
+ int rOffset = 0;
+ int gOffset = len;
+ int bOffset = len * 2;
+ int aOffset = len * 3;
+ for (int i = 0; i < len; i++) {
+ int pixel = pixels[i];
+ byte r = (byte) ((pixel >> 16) & 0xFF);
+ byte g = (byte) ((pixel >> 8) & 0xFF);
+ byte b = (byte) (pixel & 0xFF);
+ byte a = (byte) ((pixel >> 24) & 0xFF);
+ result[rOffset + i] = Zigzag.map(r);
+ result[gOffset + i] = Zigzag.map(g);
+ result[bOffset + i] = Zigzag.map(b);
+ result[aOffset + i] = Zigzag.map(a); // Alpha 单独一个平面
+ }
+ return result;
+ }
+
+ public static int[] deserializePlannarRGBAWithZigzag(byte[] data, int w, int h) throws IOException {
+ int expectedLen = w * h * 4;
+ if (data.length != expectedLen) {
+ throw new IOException("数据长度不匹配: 期望 " + expectedLen + " 字节, 实际 " + data.length + " 字节");
+ }
+ int len = w * h;
+ int[] pixels = MembandBoost.allocateUninitializedIntArray(len);
+ int rOffset = 0;
+ int gOffset = len;
+ int bOffset = len * 2;
+ int aOffset = len * 3;
+ for (int i = 0; i < len; i++) {
+ byte r = Zigzag.unmap(data[rOffset + i]);
+ byte g = Zigzag.unmap(data[gOffset + i]);
+ byte b = Zigzag.unmap(data[bOffset + i]);
+ byte a = Zigzag.unmap(data[aOffset + i]);
+ pixels[i] = ((a & 0xFF) << 24) | ((r & 0xFF) << 16) | ((g & 0xFF) << 8) | (b & 0xFF);
+ }
+ return pixels;
+ }
+
+ // ==================== 工具方法 ====================
+
+ public static BufferedImage pixelsToBufferedImage(int[] pixels, int w, int h) {
+ BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
+ int[] data = ((DataBufferInt) image.getRaster().getDataBuffer()).getData();
+ System.arraycopy(pixels, 0, data, 0, pixels.length);
+ return image;
+ }
+}
\ No newline at end of file
diff --git a/src/org/kne/codec/kif/PluginTest.java b/src/org/kne/codec/kif/PluginTest.java
new file mode 100644
index 0000000..5059972
--- /dev/null
+++ b/src/org/kne/codec/kif/PluginTest.java
@@ -0,0 +1,39 @@
+package org.kne.codec.kif;
+
+import javax.imageio.ImageIO;
+import javax.imageio.spi.IIORegistry;
+import javax.imageio.spi.ImageReaderSpi;
+import javax.imageio.spi.ImageWriterSpi;
+
+import java.util.Arrays;
+import java.util.Iterator;
+
+public class PluginTest {
+ public static void main(String[] args) {
+ KIFImageReaderSpi.register();
+ KIFImageWriterSpi.register();
+
+ IIORegistry registry = IIORegistry.getDefaultInstance();
+ // 获取所有已注册的 ImageReaderSpi
+ Iterator readerSPIs = registry.getServiceProviders(ImageReaderSpi.class, true);
+
+ System.out.println("=== 已注册的图像读取器插件 ===");
+ for (Iterator iterator = readerSPIs; iterator.hasNext();) {
+ ImageReaderSpi spi = (ImageReaderSpi) iterator.next();
+
+ System.out.println("格式: " + Arrays.toString(spi.getFormatNames()) +
+ " | 后缀: " + Arrays.toString(spi.getFileSuffixes()));
+ }
+ // 获取所有已注册的 ImageReaderSpi
+ Iterator readerSPIsx = registry.getServiceProviders(ImageWriterSpi.class, true);
+
+ System.out.println("=== 已注册的图像写入器插件 ===");
+ for (Iterator iterator = readerSPIsx; iterator.hasNext();) {
+ ImageWriterSpi spi = (ImageWriterSpi) iterator.next();
+
+ System.out.println("格式: " + Arrays.toString(spi.getFormatNames()) +
+ " | 后缀: " + Arrays.toString(spi.getFileSuffixes()));
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/src/org/kne/codec/kif/Predictor.java b/src/org/kne/codec/kif/Predictor.java
new file mode 100644
index 0000000..1fecb76
--- /dev/null
+++ b/src/org/kne/codec/kif/Predictor.java
@@ -0,0 +1,157 @@
+package org.kne.codec.kif;
+
+import org.kne.membandboost.MembandBoost;
+
+public class Predictor {
+ public static int[] predict(int[] data, int w, int h,int vx,int vy) {
+ int[] result=MembandBoost.allocateUninitializedIntArray(data.length);
+ if(vy==0) {
+ horizontalPredict0(data, result, w, h, vx, vy);
+ }else {
+ verticalPredict0(data, result, w, h, vx, vy);
+ }
+ return result;
+ }
+ public static int[] inverse(int[] data, int w, int h,int vx,int vy) {
+ int[] result=MembandBoost.allocateUninitializedIntArray( data.length);
+ if(vy==0) {
+ horizontalInverse0(data, result, w, h, vx, vy);
+ }else {
+ verticalInverse0(data, result, w, h, vx, vy);
+ }
+ return result;
+ }
+ public static void predictInPlace(int[] data, int w, int h,int vx,int vy) {
+ if(vy==0) {
+ horizontalPredict0(data, data, w, h, vx, vy);
+ }else {
+ verticalPredict0(data, data, w, h, vx, vy);
+ }
+ }
+ public static void inverseInPlace(int[] data, int w, int h,int vx,int vy) {
+ if(vy==0) {
+ horizontalInverse0(data, data, w, h, vx, vy);
+ }else {
+ verticalInverse0(data, data, w, h, vx, vy);
+ }
+ }
+
+
+ private static void horizontalPredict0(int[] datain,int[] datadout, int w, int h,int vx,int vy) {
+ for (int y = 0; y < h; y++) {
+ // 从右往左!因为左边是原始值,右边要用左边的原始值
+ for (int x = w - 1; x >= 0; x--) {
+ int idx = y * w + x;
+ int px=x+vx;
+ int py=y+vy;
+ int pred=0;
+ if(px>=0&&px=0&&py=0&&px=0&&py= 0; y--) {
+ for (int x = 0; x < w; x++) {
+ int idx = y * w + x;
+ int px=x+vx;
+ int py=y+vy;
+ int pred=0;
+ if(px>=0&&px=0&&py=0&&px=0&&py> 16) & 0xFF;
+ int ag = (a >> 8) & 0xFF;
+ int ab = a & 0xFF;
+ int aa = (a >> 24) & 0xFF;
+
+ int br = (b >> 16) & 0xFF;
+ int bg = (b >> 8) & 0xFF;
+ int bb = b & 0xFF;
+ int ba = (b >> 24) & 0xFF;
+
+ int r = ar + br;
+ int g = ag + bg;
+ int bv = ab + bb;
+ int av = aa + ba; // Alpha 通道也要处理!
+
+ // 用 &0xFF 替代 min/max,保留低 8 位(自动溢出,等同于裁剪)
+ return ((av & 0xFF) << 24) | ((r & 0xFF) << 16) | ((g & 0xFF) << 8) | (bv & 0xFF);
+ }
+
+ /**
+ * 像素安全减法:计算 a - b,每个通道独立计算,结果裁剪到 0~255
+ * 用于预测编码:残差 = 当前像素 - 预测值
+ */
+ public static int pixelSub(int a, int b) {
+ int ar = (a >> 16) & 0xFF;
+ int ag = (a >> 8) & 0xFF;
+ int ab = a & 0xFF;
+ int aa = (a >> 24) & 0xFF;
+
+ int br = (b >> 16) & 0xFF;
+ int bg = (b >> 8) & 0xFF;
+ int bb = b & 0xFF;
+ int ba = (b >> 24) & 0xFF;
+
+ int r = ar - br;
+ int g = ag - bg;
+ int bv = ab - bb;
+ int av = aa - ba; // Alpha 通道也要处理!
+
+ // 用 &0xFF 替代 min/max,保留低 8 位(自动溢出,等同于裁剪)
+ return ((av & 0xFF) << 24) | ((r & 0xFF) << 16) | ((g & 0xFF) << 8) | (bv & 0xFF);
+ }
+
+}
diff --git a/src/org/kne/codec/kif/StatisticsCollector.java b/src/org/kne/codec/kif/StatisticsCollector.java
new file mode 100644
index 0000000..24b72cd
--- /dev/null
+++ b/src/org/kne/codec/kif/StatisticsCollector.java
@@ -0,0 +1,219 @@
+package org.kne.codec.kif;
+
+import java.text.DecimalFormat;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.stream.Collectors;
+
+/**
+ * 通用统计收集器(线程安全)
+ * 统计任意对象的出现次数与概率
+ *
+ * @param 被统计的对象类型
+ */
+public class StatisticsCollector {
+
+ protected final ConcurrentHashMap counts = new ConcurrentHashMap<>();
+ protected final AtomicLong total = new AtomicLong(0);
+ protected final DecimalFormat df = new DecimalFormat("0.00");
+
+ /**
+ * 记录一次出现
+ */
+ public void addRecord(T item) {
+ counts.computeIfAbsent(item, k -> new AtomicLong(0)).incrementAndGet();
+ total.incrementAndGet();
+ }
+
+ /**
+ * 记录多次出现
+ */
+ public void addRecord(T item, long count) {
+ counts.computeIfAbsent(item, k -> new AtomicLong(0)).addAndGet(count);
+ total.addAndGet(count);
+ }
+
+ /**
+ * 记录另一个收集器的所有数据
+ */
+ public void merge(StatisticsCollector other) {
+ for (Map.Entry entry : other.counts.entrySet()) {
+ addRecord(entry.getKey(), entry.getValue().get());
+ }
+ }
+
+ /**
+ * 获取总次数
+ */
+ public long getTotal() {
+ return total.get();
+ }
+
+ /**
+ * 获取某个对象的出现次数
+ */
+ public long getCount(T item) {
+ AtomicLong count = counts.get(item);
+ return count == null ? 0 : count.get();
+ }
+
+ /**
+ * 获取某个对象的出现概率(百分比)
+ */
+ public double getProbability(T item) {
+ long t = total.get();
+ if (t == 0) return 0.0;
+ return getCount(item) * 100.0 / t;
+ }
+
+ /**
+ * 获取去重对象数量
+ */
+ public int getUniqueCount() {
+ return counts.size();
+ }
+
+ /**
+ * 获取所有对象(按出现次数降序排列)
+ */
+ public Map getSorted() {
+ return counts.entrySet().stream()
+ .sorted((a, b) -> Long.compare(b.getValue().get(), a.getValue().get()))
+ .collect(Collectors.toMap(
+ Map.Entry::getKey,
+ Map.Entry::getValue,
+ (old, neu) -> old,
+ java.util.LinkedHashMap::new
+ ));
+ }
+
+ /**
+ * 获取所有对象(按出现次数升序排列)
+ */
+ public Map getSortedAscending() {
+ return counts.entrySet().stream()
+ .sorted((a, b) -> Long.compare(a.getValue().get(), b.getValue().get()))
+ .collect(Collectors.toMap(
+ Map.Entry::getKey,
+ Map.Entry::getValue,
+ (old, neu) -> old,
+ java.util.LinkedHashMap::new
+ ));
+ }
+
+ /**
+ * 重置统计
+ */
+ public void reset() {
+ counts.clear();
+ total.set(0);
+ }
+
+ /**
+ * 判断是否为空
+ */
+ public boolean isEmpty() {
+ return counts.isEmpty();
+ }
+
+ /**
+ * 生成表格形式的统计报告(需要子类实现格式化)
+ */
+ public String formatTable(ItemFormatter formatter) {
+ long t = total.get();
+ if (t == 0) {
+ return "(无统计数据)";
+ }
+
+ StringBuilder sb = new StringBuilder();
+ sb.append("╔═══════════════════════════════════════════════════════════════════════════════════╗\n");
+ sb.append("║ 统计报告 ║\n");
+ sb.append("╠═══════════════════════════════════════════════════════════════════════════════════╣\n");
+ sb.append("║ 序号 │ 对象 │ 出现次数 │ 占比 │ 累积占比 ║\n");
+ sb.append("╠═══════════════════════════════════════════════════════════════════════════════════╣\n");
+
+ Map sorted = getSorted();
+ long cumulative = 0;
+ int index = 0;
+
+ for (Map.Entry entry : sorted.entrySet()) {
+ T item = entry.getKey();
+ long count = entry.getValue().get();
+ double pct = count * 100.0 / t;
+ cumulative += count;
+ double cumPct = cumulative * 100.0 / t;
+ index++;
+
+ String itemStr = formatter.format(item);
+ // 截断过长的字符串
+ if (itemStr.length() > 27) {
+ itemStr = itemStr.substring(0, 24) + "...";
+ }
+
+ sb.append(String.format("║ %4d │ %-27s │ %8d │ %6.2f%% │ %6.2f%% ║\n",
+ index,
+ itemStr,
+ count,
+ pct,
+ cumPct
+ ));
+ }
+
+ sb.append("╠═══════════════════════════════════════════════════════════════════════════════════╣\n");
+ sb.append(String.format("║ 总次数: %d │ 唯一对象数: %d │ 覆盖率: %6.2f%% ║\n",
+ t,
+ getUniqueCount(),
+ 100.0
+ ));
+ sb.append("╚═══════════════════════════════════════════════════════════════════════════════════╝");
+ return sb.toString();
+ }
+
+ /**
+ * 生成 CSV 格式报告
+ */
+ public String toCSV(ItemFormatter formatter) {
+ long t = total.get();
+ if (t == 0) return "无数据";
+
+ StringBuilder sb = new StringBuilder();
+ sb.append("序号,对象,次数,占比(%),累积占比(%)\n");
+
+ Map sorted = getSorted();
+ long cumulative = 0;
+ int index = 0;
+
+ for (Map.Entry entry : sorted.entrySet()) {
+ T item = entry.getKey();
+ long count = entry.getValue().get();
+ double pct = count * 100.0 / t;
+ cumulative += count;
+ double cumPct = cumulative * 100.0 / t;
+ index++;
+
+ sb.append(String.format("%d,%s,%d,%.2f,%.2f\n",
+ index,
+ formatter.format(item),
+ count,
+ pct,
+ cumPct
+ ));
+ }
+ return sb.toString();
+ }
+
+ @Override
+ public String toString() {
+ return formatTable((t)->{
+ return t.toString();
+ });
+ }
+
+ /**
+ * 对象格式化接口
+ */
+ public interface ItemFormatter {
+ String format(T item);
+ }
+}
\ No newline at end of file
diff --git a/src/org/kne/codec/kif/Zigzag.java b/src/org/kne/codec/kif/Zigzag.java
new file mode 100644
index 0000000..96d8d28
--- /dev/null
+++ b/src/org/kne/codec/kif/Zigzag.java
@@ -0,0 +1,59 @@
+package org.kne.codec.kif;
+
+/**
+ * Zigzag 映射工具
+ * 用于将预测残差(集中在0附近的有符号数)映射为熵编码友好的非负数序列
+ */
+public class Zigzag {
+
+ /**
+ * 映射单个 byte
+ * 0→0, -1→1, 1→2, -2→3, 2→4, ...
+ */
+ public static byte map(byte x) {
+ int v = x; // byte 转为 int(保留符号)
+ return (byte) ((v >= 0) ? (v << 1) : ((-v << 1) - 1));
+ }
+
+ /**
+ * 逆映射单个 byte
+ * 0→0, 1→-1, 2→1, 3→-2, 4→2, ...
+ *
+ * 用按位与替代取余:v & 1 等价于 v % 2,但快 2-3 倍
+ */
+ public static byte unmap(byte y) {
+ int v = y & 0xFF; // 转为无符号 0~255
+ return (byte) ((v & 1) == 0 ? (v >> 1) : (-((v + 1) >> 1)));
+ }
+
+ /**
+ * 映射整个字节数组(原地修改)
+ */
+ public static void mapInPlace(byte[] data) {
+ for (int i = 0; i < data.length; i++) {
+ data[i] = map(data[i]);
+ }
+ }
+
+ /**
+ * 逆映射整个字节数组(原地修改)
+ */
+ public static void unmapInPlace(byte[] data) {
+ for (int i = 0; i < data.length; i++) {
+ data[i] = unmap(data[i]);
+ }
+ }
+
+ /**
+ * 映射整个 int 数组(每个元素的低 8 位)
+ * 注意:只处理每个 int 的低 8 位,高位保持不变
+ */
+ public static void mapInPlaceInt(int[] data) {
+ for (int i = 0; i < data.length; i++) {
+ int v = data[i] & 0xFF; // 取低 8 位作为有符号数
+ // 处理符号扩展:如果最高位为 1,说明是负数
+ byte b = (byte) v;
+ data[i] = (data[i] & 0xFFFFFF00) | (map(b) & 0xFF);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/org/kne/codec/kvf/H264ToKVF.java b/src/org/kne/codec/kvf/H264ToKVF.java
new file mode 100644
index 0000000..015220e
--- /dev/null
+++ b/src/org/kne/codec/kvf/H264ToKVF.java
@@ -0,0 +1,76 @@
+package org.kne.codec.kvf;
+
+import org.jcodec.api.FrameGrab;
+import org.jcodec.api.JCodecException;
+import org.jcodec.common.io.FileChannelWrapper;
+import org.jcodec.common.io.SeekableByteChannel;
+import org.jcodec.common.model.Picture;
+import org.jcodec.containers.mp4.demuxer.MP4Demuxer;
+import org.jcodec.scale.AWTUtil;
+
+import javax.imageio.ImageIO;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.nio.channels.FileChannel;
+import java.nio.charset.StandardCharsets;
+import java.util.zip.ZipFile;
+
+/**
+ * JCodec 最小化 Demo:读取 MP4 文件,解码每一帧为 BufferedImage
+ *
+ * 用法:java JCodecDemo /path/to/video.mp4
+ */
+public class H264ToKVF {
+
+ public static void main(String[] args) throws IOException, JCodecException {
+ // 1. 注册 KIF 插件
+ org.kne.codec.kif.KIFImageReaderSpi.register();
+ org.kne.codec.kif.KIFImageWriterSpi.register();
+ System.setOut(new PrintStream(System.out, true, StandardCharsets.UTF_8));
+ System.setErr(new PrintStream(System.err, true, StandardCharsets.UTF_8));
+ if (args.length < 1) {
+ System.err.println("用法: java JCodecDemo ");
+ System.exit(1);
+ }
+
+ File inputFile = new File(args[0]);
+ if (!inputFile.exists()) {
+ System.err.println("文件不存在: " + inputFile.getAbsolutePath());
+ System.exit(1);
+ }
+ File outputFile=new File("output.kvf");
+
+ System.out.println("🎬 开始转码: " + inputFile.getName());
+
+ // 1. 打开 FileChannel,向上转型为 SeekableByteChannel
+ try ( KVFOutputStream kvo=new KVFOutputStream(new FileOutputStream(outputFile))){
+ try (FileInputStream fis = new FileInputStream(inputFile);
+ FileChannel fileChannel = fis.getChannel()) {
+
+ // 关键:用 SeekableByteChannel 类型接收
+ SeekableByteChannel channel =new FileChannelWrapper( fileChannel);
+ // 2. 创建帧抓取器
+ MP4Demuxer demux=MP4Demuxer.createMP4Demuxer(channel);
+ // 3. 逐帧解码
+ int frameCount = 0;
+ long startTime = System.currentTimeMillis();
+
+ Picture picture;
+ /* while ((picture = grab.getNativeFrame()) != null) {
+ frameCount++;
+
+ System.out.printf(" 已解码 %d 帧...\n", frameCount);
+ kvo.writeFrame(0, frameCount, picture, "kif");
+ }*/
+
+ long elapsed = System.currentTimeMillis() - startTime;
+ System.out.printf("✅ 转码完成!共 %d 帧,耗时 %dms\n", frameCount, elapsed);
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/org/kne/codec/kvf/KVFCodec.java b/src/org/kne/codec/kvf/KVFCodec.java
new file mode 100644
index 0000000..610e9f4
--- /dev/null
+++ b/src/org/kne/codec/kvf/KVFCodec.java
@@ -0,0 +1,94 @@
+package org.kne.codec.kvf;
+
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.util.List;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
+
+import javax.imageio.ImageIO;
+
+import org.jcodec.common.model.Picture;
+import org.jcodec.scale.AWTUtil;
+import org.kne.opencl64.OpenCLDevice;
+import org.kne.opencl64.concurrent.OpenCLExecutors;
+
+public class KVFCodec {
+ private static ThreadPoolExecutor cpupool ;
+ static {
+ cpupool= (ThreadPoolExecutor) Executors.newFixedThreadPool(
+ Runtime.getRuntime().availableProcessors(),
+ new ThreadFactory() {
+ private final AtomicInteger threadNumber = new AtomicInteger(1);
+ @Override
+ public Thread newThread(Runnable r) {
+ Thread t = new Thread(r, "KVFCodec-CPU-Worker-" + threadNumber.getAndIncrement());
+ t.setDaemon(true); // 设置为守护线程
+ return t;
+ }
+ }
+ );
+ }
+ public static Future encodeIFrame(BufferedImage frame,String formattype,Consumerresult) {
+ cpuBackPressure();
+ return cpupool.submit(()->{
+ ByteArrayOutputStream boos=new ByteArrayOutputStream();
+ ImageIO.write(frame, formattype, boos);
+ byte[]resultdata=boos.toByteArray();
+ if(result!=null) {
+ result.accept(resultdata);
+ }
+ return resultdata;
+ });
+ }
+
+ public static Future encodeIFrame(Picture picture,String formattype,Consumerresult) {
+ cpuBackPressure();
+ return cpupool.submit(()->{
+ BufferedImage frame = AWTUtil.toBufferedImage(picture);
+ ByteArrayOutputStream boos=new ByteArrayOutputStream();
+ ImageIO.write(frame, formattype, boos);
+ byte[]resultdata=boos.toByteArray();
+ if(result!=null) {
+ result.accept(resultdata);
+ }
+ return resultdata;
+ });
+ }
+
+ public static Future decodeIFrame(byte[]data,Consumerresult){
+ cpuBackPressure();
+ return cpupool.submit(()->{
+ ByteArrayInputStream biis=new ByteArrayInputStream(data);
+ BufferedImage resultframe= ImageIO.read(biis);
+ biis.close();
+ if(result!=null) {
+ result.accept(resultframe);
+ }
+ return resultframe;
+ });
+ }
+
+
+ public static void cpuBackPressure() {
+ while(isCPUOverloaded()) {
+ try {
+ Thread.sleep(1);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ /**
+ * 判断是否过载(调用方可据此决定是否降级处理)
+ */
+ public static boolean isCPUOverloaded() {
+ return cpupool.getQueue().size() > cpupool.getPoolSize() * 2;
+ }
+}
diff --git a/src/org/kne/codec/kvf/KVFOutputStream.java b/src/org/kne/codec/kvf/KVFOutputStream.java
new file mode 100644
index 0000000..738f0f7
--- /dev/null
+++ b/src/org/kne/codec/kvf/KVFOutputStream.java
@@ -0,0 +1,150 @@
+package org.kne.codec.kvf;
+
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.zip.Deflater;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+import javax.imageio.ImageIO;
+
+import org.jcodec.common.model.Picture;
+import org.jcodec.scale.AWTUtil;
+import org.kne.codec.kif.ExceptionTool;
+
+public class KVFOutputStream extends ZipOutputStream {
+ private ReentrantLock lock=new ReentrantLock();
+ private List>tasks=Collections.synchronizedList(new ArrayList<>());
+ public KVFOutputStream(OutputStream out, Charset charset) {
+ super(out, charset);
+ setLevel(Deflater.NO_COMPRESSION);
+ }
+
+ public KVFOutputStream(OutputStream out) {
+ super(out);
+ setLevel(Deflater.NO_COMPRESSION);
+ }
+
+
+ /**
+ * 写入一个条目(自动处理锁)
+ */
+ public void putEntry(String name, byte[] data) throws IOException {
+ lock.lock();
+ try {
+ putNextEntry(new ZipEntry(name));
+ write(data);
+ closeEntry();
+ } finally {
+ lock.unlock();
+ }
+ }
+
+
+
+ /**
+ * 写入 meta.json(自动使用 UTF-8)
+ */
+ public void writeMeta(String json) throws IOException {
+ putEntry("meta.json", json.getBytes(StandardCharsets.UTF_8));
+ }
+
+
+ /**
+ * 写入一个帧(KIF 数据)
+ */
+ public void writeFrame(int channelIndex, int frameIndex, byte[] kifData,String format) throws IOException {
+ putEntry(String.format("video/%d/frame_%04d.%s",channelIndex, frameIndex,format), kifData);
+ }
+ //private List
+ public void writeFrame(int channelIndex, int frameIndex,BufferedImage frame,String format) throws IOException {
+ removeDone();
+ FutureencodeFuture= KVFCodec.encodeIFrame(frame, format, (result)->{
+ try {
+ writeFrame(channelIndex, frameIndex, result,format);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ });
+ tasks.add(encodeFuture);
+ }
+
+ public void writeFrame(int channelIndex, int frameIndex, Picture picture, String format) throws IOException {
+ removeDone();
+ FutureencodeFuture= KVFCodec.encodeIFrame(picture, format, (result)->{
+ try {
+ writeFrame(channelIndex, frameIndex, result,format);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ });
+ tasks.add(encodeFuture);
+ }
+
+ private void waitForDone()throws IOException {
+ while(true) {
+ removeDone();
+ if(tasks.isEmpty()) {
+ return;
+ }
+ try {
+ Thread.sleep(1);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ private void removeDone()throws IOException {
+ AtomicReferencee=new AtomicReference<>();
+ tasks.removeIf((v)->{
+ boolean b=v.isDone();
+ if(b!=false) {
+ try{
+ v.get();
+ }catch(ExecutionException | InterruptedException thr) {
+ if(e.get()==null) {
+ e.set(thr);
+ }
+ }
+ }
+ return b;
+ });
+ Throwable th=e.get();
+ ExceptionTool.throwIOException(th);
+ }
+
+ @Override
+ public void finish() throws IOException {
+ waitForDone();
+ lock.lock();
+ try {
+ super.finish();
+ } finally {
+ lock.unlock();
+ }
+ }
+
+
+ @Override
+ public void close() throws IOException {
+ waitForDone();
+ lock.lock();
+ try {
+ super.close();
+ } finally {
+ lock.unlock();
+ }
+ }
+}
diff --git a/src/org/kne/codec/kvf/MP4Analyzer.java b/src/org/kne/codec/kvf/MP4Analyzer.java
new file mode 100644
index 0000000..e37f446
--- /dev/null
+++ b/src/org/kne/codec/kvf/MP4Analyzer.java
@@ -0,0 +1,269 @@
+package org.kne.codec.kvf;
+
+import org.jcodec.common.DemuxerTrack;
+import org.jcodec.common.DemuxerTrackMeta;
+import org.jcodec.common.io.NIOUtils;
+import org.jcodec.common.io.SeekableByteChannel;
+import org.jcodec.common.model.Packet;
+import org.jcodec.common.model.Picture;
+import org.jcodec.common.model.Size;
+import org.jcodec.containers.mp4.MP4TrackType;
+import org.jcodec.containers.mp4.boxes.*;
+import org.jcodec.containers.mp4.demuxer.AbstractMP4DemuxerTrack;
+import org.jcodec.containers.mp4.demuxer.MP4Demuxer;
+import org.jcodec.containers.mp4.demuxer.MP4DemuxerTrack;
+
+import java.io.File;
+import java.io.PrintStream;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+
+/**
+ * MP4 元数据分析器
+ * 获取音视频轨道、帧率、GOP、编码格式等完整元数据
+ */
+public class MP4Analyzer {
+
+ public static void main(String[] args) throws Exception {
+ System.setOut(new PrintStream(System.out, true, StandardCharsets.UTF_8));
+ System.setErr(new PrintStream(System.err, true, StandardCharsets.UTF_8));
+ if (args.length < 1) {
+ System.err.println("用法: java MP4Analyzer ");
+ System.exit(1);
+ }
+
+ File file = new File(args[0]);
+ if (!file.exists()) {
+ System.err.println("文件不存在: " + file.getAbsolutePath());
+ System.exit(1);
+ }
+
+ System.out.println("========================================");
+ System.out.println("📹 MP4 元数据分析: " + file.getName());
+ System.out.println("========================================\n");
+
+ try (SeekableByteChannel channel = NIOUtils.readableChannel(file);
+ MP4Demuxer demuxer = MP4Demuxer.createMP4Demuxer(channel)) {
+
+ // 1. 视频轨道信息
+ analyzeVideoTrack(demuxer);
+
+ // 2. 音频轨道信息
+ analyzeAudioTracks(demuxer);
+
+ // 3. 其他轨道
+ analyzeOtherTracks(demuxer);
+
+ // 4. 容器元数据
+ analyzeContainerInfo(demuxer);
+
+ // 5. GOP 结构分析
+ analyzeGOP(demuxer);
+
+ // 6. 统计信息
+ printSummary(demuxer);
+ }
+ }
+
+ /**
+ * 分析视频轨道
+ */
+ private static void analyzeVideoTrack(MP4Demuxer demuxer) throws Exception {
+ System.out.println("🎬 视频轨道 (Video Tracks)");
+ System.out.println("───────────────────────────────────────────────");
+
+ List videoTracks = demuxer.getVideoTracks();
+ if (videoTracks.isEmpty()) {
+ System.out.println(" ❌ 无视频轨道");
+ System.out.println();
+ return;
+ }
+
+ for (int i = 0; i < videoTracks.size(); i++) {
+ DemuxerTrack track = videoTracks.get(i);
+ DemuxerTrackMeta meta = track.getMeta();
+
+ System.out.printf(" 轨道 %d:\n", i);
+ System.out.printf(" 编码格式: %s\n", meta.getCodec());
+ System.out.printf(" 分辨率: %dx%d\n", meta.getVideoCodecMeta().getSize().getWidth(),
+ meta.getVideoCodecMeta().getSize().getHeight());
+ System.out.printf(" 帧率: %.3f fps\n", meta.getTotalFrames() / meta.getTotalDuration());
+ System.out.printf(" 总帧数: %d\n", meta.getTotalFrames());
+ System.out.printf(" 时长: %.2f 秒\n", meta.getTotalDuration());
+ System.out.printf(" 像素格式: %s\n", meta.getVideoCodecMeta().getColor());
+ // System.out.printf(" 参考帧数: %d\n", meta.getVideoCodecMeta().getRefFrames());
+ // System.out.printf(" 是否可变帧率: %s\n", meta.isVariableFps() ? "是" : "否");
+
+ // 尝试获取更多信息
+ if (track instanceof MP4DemuxerTrack) {
+ MP4DemuxerTrack m4Track = (MP4DemuxerTrack) track;
+ // System.out.printf(" 轨道 ID: %d\n", m4Track.getTrackId());
+ System.out.printf(" 轨道类型: %s\n", m4Track.getType());
+ }
+
+ System.out.println();
+ }
+ }
+
+ /**
+ * 分析音频轨道
+ */
+ private static void analyzeAudioTracks(MP4Demuxer demuxer) {
+ System.out.println("🎵 音频轨道 (Audio Tracks)");
+ System.out.println("───────────────────────────────────────────────");
+
+ List audioTracks = demuxer.getAudioTracks();
+ if (audioTracks.isEmpty()) {
+ System.out.println(" ❌ 无音频轨道");
+ System.out.println();
+ return;
+ }
+
+ for (int i = 0; i < audioTracks.size(); i++) {
+ DemuxerTrack track = audioTracks.get(i);
+ DemuxerTrackMeta meta = track.getMeta();
+
+ System.out.printf(" 轨道 %d:\n", i);
+ System.out.printf(" 编码格式: %s\n", meta.getCodec());
+ System.out.printf(" 采样率: %d Hz\n", meta.getAudioCodecMeta().getSampleRate());
+ System.out.printf(" 声道数: %d\n", meta.getAudioCodecMeta().getChannelCount());
+ System.out.printf(" 采样位数: %d bit\n", meta.getAudioCodecMeta().getSampleSize());
+ System.out.printf(" 总帧数: %d\n", meta.getTotalFrames());
+ System.out.printf(" 时长: %.2f 秒\n", meta.getTotalDuration());
+ System.out.println();
+ }
+ }
+
+ /**
+ * 分析其他轨道(字幕等)
+ */
+ private static void analyzeOtherTracks(MP4Demuxer demuxer) {
+ System.out.println("📝 其他轨道 (Other Tracks)");
+ System.out.println("───────────────────────────────────────────────");
+
+ List otherTracks = demuxer.getTracks();
+ if (otherTracks.isEmpty()) {
+ System.out.println(" ❌ 无其他轨道");
+ System.out.println();
+ return;
+ }
+
+ for (DemuxerTrack track : otherTracks) {
+ DemuxerTrackMeta meta = track.getMeta();
+ System.out.printf(" 轨道类型: %s, 编码: %s\n",
+ meta.getCodec(), meta.getCodec());
+ }
+ System.out.println();
+ }
+
+ /**
+ * 分析容器元数据
+ */
+ private static void analyzeContainerInfo(MP4Demuxer demuxer) {
+ System.out.println("📦 容器信息 (Container Info)");
+ System.out.println("───────────────────────────────────────────────");
+
+ // 获取 FileTypeBox (ftyp)
+ /* FileTypeBox ftyp = demuxer.getBoxes(FileTypeBox.class).get(0);
+ System.out.printf(" 主要品牌: %s\n", ftyp.getMajorBrand());
+ System.out.printf(" 兼容品牌: %s\n", String.join(", ", ftyp.getCompatibleBrands()));
+ System.out.printf(" 版本: %d\n", ftyp.getVersion());
+*/
+ // 获取 MovieBox (moov)
+ MovieBox moov = demuxer.getMovie();
+ System.out.printf(" 时长 (timescale): %d\n", moov.getTimescale());
+ System.out.println();
+ }
+
+ /**
+ * 分析 GOP 结构
+ */
+ private static void analyzeGOP(MP4Demuxer demuxer) throws Exception {
+ System.out.println("🎯 GOP 结构分析 (GOP Structure)");
+ System.out.println("───────────────────────────────────────────────");
+
+ List videoTracks = demuxer.getVideoTracks();
+ if (videoTracks.isEmpty()) {
+ System.out.println(" ❌ 无视频轨道");
+ System.out.println();
+ return;
+ }
+
+ DemuxerTrack videoTrack = videoTracks.get(0);
+ DemuxerTrackMeta meta = videoTrack.getMeta();
+
+ // 获取 I 帧位置
+ int[] seekFrames = meta.getSeekFrames();
+ if (seekFrames == null || seekFrames.length == 0) {
+ System.out.println(" ⚠️ 无法获取 I 帧位置");
+ System.out.println();
+ return;
+ }
+
+ System.out.printf(" I 帧数量: %d\n", seekFrames.length);
+ System.out.printf(" 平均 GOP 大小: %.1f 帧\n",
+ (double) meta.getTotalFrames() / seekFrames.length);
+
+ // 显示前 10 个 GOP
+ System.out.println(" 前 10 个 I 帧位置:");
+ for (int i = 0; i < Math.min(10, seekFrames.length); i++) {
+ int nextIFrame = (i + 1 < seekFrames.length) ? seekFrames[i + 1] : meta.getTotalFrames();
+ int gopSize = nextIFrame - seekFrames[i];
+ System.out.printf(" GOP %d: I 帧位置 %d, GOP 大小 %d 帧\n",
+ i, seekFrames[i], gopSize);
+ }
+
+ // 检查是否有 B 帧
+ boolean hasBFrame = false;
+ try {
+ // 重置轨道位置
+ // 读取前 100 帧判断
+ // 这里简化判断
+ } catch (Exception e) {
+ // 忽略
+ }
+ System.out.println();
+ }
+
+ /**
+ * 打印统计摘要
+ */
+ private static void printSummary(MP4Demuxer demuxer) {
+ System.out.println("📊 统计摘要");
+ System.out.println("───────────────────────────────────────────────");
+
+ List videoTracks = demuxer.getVideoTracks();
+ List audioTracks = demuxer.getAudioTracks();
+
+ System.out.printf(" 视频轨道: %d\n", videoTracks.size());
+ System.out.printf(" 音频轨道: %d\n", audioTracks.size());
+
+ if (!videoTracks.isEmpty()) {
+ DemuxerTrackMeta meta = videoTracks.get(0).getMeta();
+ System.out.printf(" 视频编码: %s\n", meta.getCodec());
+ Size size = meta.getVideoCodecMeta().getSize();
+ System.out.printf(" 分辨率: %dx%d\n", size.getWidth(), size.getHeight());
+ System.out.printf(" 帧率: %.3f fps\n", meta.getTotalFrames() / meta.getTotalDuration());
+ System.out.printf(" 总帧数: %d\n", meta.getTotalFrames());
+ System.out.printf(" 时长: %.2f 秒 (%.2f 分钟)\n",
+ meta.getTotalDuration(), meta.getTotalDuration() / 60.0);
+
+ int[] seekFrames = meta.getSeekFrames();
+ if (seekFrames != null) {
+ System.out.printf(" GOP 数量: %d\n", seekFrames.length);
+ System.out.printf(" 平均 GOP 大小: %.1f 帧\n",
+ (double) meta.getTotalFrames() / seekFrames.length);
+ }
+ }
+
+ if (!audioTracks.isEmpty()) {
+ DemuxerTrackMeta meta = audioTracks.get(0).getMeta();
+ System.out.printf(" 音频编码: %s\n", meta.getCodec());
+ System.out.printf(" 采样率: %d Hz\n", meta.getAudioCodecMeta().getSampleRate());
+ System.out.printf(" 声道数: %d\n", meta.getAudioCodecMeta().getChannelCount());
+ }
+
+ System.out.println();
+ System.out.println("✅ 分析完成!");
+ }
+}
\ No newline at end of file
diff --git a/src/org/kne/debug/Debuger.java b/src/org/kne/debug/Debuger.java
new file mode 100644
index 0000000..d175d69
--- /dev/null
+++ b/src/org/kne/debug/Debuger.java
@@ -0,0 +1,129 @@
+package org.kne.debug;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+
+import java.util.Set;
+final class Int implements Comparable,Cloneable {
+ public int value;
+
+ public Int(int value) {
+ super();
+ this.value = value;
+ }
+
+ @Override
+ public int hashCode() {
+ return value;
+ }
+
+ @Override
+ public Object clone() throws CloneNotSupportedException {
+ // TODO �Զ����ɵķ������
+ return super.clone();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ Int other = (Int) obj;
+ if (value != other.value)
+ return false;
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return new Integer(value).toString();
+ }
+
+ @Override
+ public int compareTo(Int o) {
+
+ return o.value-value;
+ }
+
+
+
+}
+
+public class Debuger extends Thread {
+ Map ,Object[]>md=new HashMap,Object[]>();
+ @Override
+ public void run() {
+ while(true){
+ for(int iz=0;iz<100;iz++){
+ Map m=Thread.getAllStackTraces();
+ Set> s=m.entrySet();
+ Iterator> i=s.iterator();
+ while(i.hasNext()){
+ Entryelements =i.next();
+ StackTraceElement[]value=elements.getValue();
+ List l=new ArrayList(value.length);
+ for (int j = 0; j < value.length; j++) {
+ l.add(value[j]);
+ }
+ if(!l.isEmpty())
+ if(md.containsKey(l)){
+ ((Int)md.get(l)[0]).value++;
+ }else{
+ md.put(l,new Object[]{new Int(1),elements.getKey()} );
+ }
+ }
+
+ }
+ ArrayList, Object[]>> tmp=new ArrayList();
+ Set, Object[]>> sn=md.entrySet();
+ Iterator, Object[]>> in=sn.iterator();
+ while(in.hasNext()){
+ Entry, Object[]>elements =in.next();
+
+ tmp.add(elements);
+ }
+ tmp.sort(new Comparator, Object[]>>() {
+
+ @Override
+ public int compare(Entry, Object[]> o1,
+ Entry, Object[]> o2) {
+
+ return ((Int)o2.getValue()[0]).value-((Int)o1.getValue()[0]).value;
+ }
+ });
+ for(int d=0;d, Object[]>elements=tmp.get(d);
+ if(((Thread)(elements.getValue()[1])).getName().startsWith("Decode")){
+
+ }else{
+ continue;
+ }
+ List k=elements.getKey();
+ StringBuilder sb=new StringBuilder();
+ for(int d2=0;d2 points;
+ private long lastNanoTime;
+ private long startNanoTime;
+
+ /**
+ * 创建一个计时器
+ * @param name 本次测量的名称(用于输出标题)
+ * @param enabled true=启用计时,false=所有操作跳过(零开销)
+ */
+ public TimeDebugger(String name, boolean enabled) {
+ this.enabled = enabled;
+ this.name = name;
+ // enabled=false 时不分配 ArrayList 内存
+ this.points = enabled ? new ArrayList<>() : null;
+ if (enabled) {
+ this.lastNanoTime = System.nanoTime();
+ this.startNanoTime = this.lastNanoTime;
+ } else {
+ this.lastNanoTime = 0;
+ this.startNanoTime = 0;
+ }
+ }
+
+ /**
+ * 标记一个时间点
+ * @param label 当前阶段的名称
+ */
+ public void mark(String label) {
+ if (!enabled) return;
+ long now = System.nanoTime();
+ long durationNs = now - lastNanoTime;
+ lastNanoTime = now;
+ points.add(new TimingPoint(label, durationNs));
+ }
+
+ /**
+ * 标记一个时间点,并打印到控制台(实时输出,方便跟踪长流程)
+ * @param label 当前阶段的名称
+ */
+ public void markAndPrint(String label) {
+ if (!enabled) return;
+ mark(label);
+ System.out.printf(" ⏱️ %s: %.3fms\n", label, getLastDurationMs());
+ }
+
+ /**
+ * 获取上一个阶段的耗时(毫秒)
+ */
+ public double getLastDurationMs() {
+ if (!enabled || points.isEmpty()) return 0;
+ return points.get(points.size() - 1).durationMs();
+ }
+
+ /**
+ * 获取总耗时(毫秒)
+ */
+ public double getTotalMs() {
+ if (!enabled) return 0;
+ return (lastNanoTime - startNanoTime) / 1_000_000.0;
+ }
+
+ /**
+ * 打印所有计时结果
+ */
+ public void print() {
+ if (!enabled) return;
+ StringBuilder sb = new StringBuilder();
+ sb.append("═══════════════════════════════════════════════\n");
+ sb.append(" ⏱️ ").append(name).append("\n");
+ sb.append("═══════════════════════════════════════════════\n");
+
+ double total = 0;
+ for (int i = 0; i < points.size(); i++) {
+ TimingPoint p = points.get(i);
+ double ms = p.durationMs();
+ total += ms;
+ sb.append(String.format(" %-20s: %8.3fms (累计: %8.3fms)\n",
+ p.label, ms, total));
+ }
+
+ sb.append("───────────────────────────────────────────────\n");
+ sb.append(String.format(" %-20s: %8.3fms\n", "总计", total));
+ sb.append("═══════════════════════════════════════════════");
+ System.err.println(sb.toString());
+ }
+
+ /**
+ * 重置计时器(不清除已有记录,用于分段测量)
+ */
+ public void reset() {
+ if (!enabled) return;
+ this.lastNanoTime = System.nanoTime();
+ this.startNanoTime = this.lastNanoTime;
+ if (points != null) {
+ this.points.clear();
+ }
+ }
+
+ /**
+ * 检查是否启用
+ */
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ /**
+ * 内部计时点
+ */
+ private static final class TimingPoint {
+ final String label;
+ final long durationNs;
+
+ TimingPoint(String label, long durationNs) {
+ this.label = label;
+ this.durationNs = durationNs;
+ }
+
+ double durationMs() {
+ return durationNs / 1_000_000.0;
+ }
+ }
+
+ // ==================== 静态方法:全局默认实例 ====================
+
+ private static TimeDebugger defaultDebugger;
+
+ /**
+ * 获取全局默认计时器(简化用法,不需要 new)
+ * 默认启用,如需关闭请使用 get(boolean)
+ */
+ public static TimeDebugger get() {
+ return get(true);
+ }
+
+ /**
+ * 获取全局默认计时器(可指定开关)
+ * @param enabled true=启用,false=关闭
+ */
+ public static TimeDebugger get(boolean enabled) {
+ if (defaultDebugger == null || defaultDebugger.isEnabled() != enabled) {
+ defaultDebugger = new TimeDebugger("默认流程", enabled);
+ }
+ return defaultDebugger;
+ }
+
+ /**
+ * 重置全局默认计时器
+ */
+ public static void resetDefault() {
+ if (defaultDebugger != null) {
+ defaultDebugger.reset();
+ }
+ }
+}
\ No newline at end of file