diff --git a/.idea/libraries/badlogicgames_gdx.xml b/.idea/libraries/badlogicgames_gdx.xml
new file mode 100644
index 0000000..cd1db0b
--- /dev/null
+++ b/.idea/libraries/badlogicgames_gdx.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/libraries/badlogicgames_gdx_backend_lwjgl3.xml b/.idea/libraries/badlogicgames_gdx_backend_lwjgl3.xml
new file mode 100644
index 0000000..5302a7a
--- /dev/null
+++ b/.idea/libraries/badlogicgames_gdx_backend_lwjgl3.xml
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/2taud.sh b/2taud.sh
index 6266dff..e44bfcc 100755
--- a/2taud.sh
+++ b/2taud.sh
@@ -1,8 +1,8 @@
#!/usr/bin/env fish
-for f in *.mod; python3 mod2taud.py $f assets/disk0/(basename $f .mod).taud; end
-for f in *.s3m; python3 s3m2taud.py $f assets/disk0/(basename $f .s3m).taud; end
-for f in *.it; python3 it2taud.py $f assets/disk0/(basename $f .it).taud; end
-for f in *.xm; python3 xm2taud.py $f assets/disk0/(basename $f .xm).taud; end
-for f in *.mon; python3 mon2taud.py $f assets/disk0/(basename $f .mon).taud; end
-for f in *.MON; python3 mon2taud.py $f assets/disk0/(basename $f .MON).taud; end
+for f in *.mod; python3 mod2taud.py $f assets/disk0/home/music/(basename $f .mod).taud; end
+for f in *.s3m; python3 s3m2taud.py $f assets/disk0/home/music/(basename $f .s3m).taud; end
+for f in *.it; python3 it2taud.py $f assets/disk0/home/music/(basename $f .it).taud; end
+for f in *.xm; python3 xm2taud.py $f assets/disk0/home/music/(basename $f .xm).taud; end
+for f in *.mon; python3 mon2taud.py $f assets/disk0/home/music/(basename $f .mon).taud; end
+for f in *.MON; python3 mon2taud.py $f assets/disk0/home/music/(basename $f .MON).taud; end
diff --git a/TerranBASICexecutable/TerranBASICexecutable.iml b/TerranBASICexecutable/TerranBASICexecutable.iml
index d98a81a..5829148 100644
--- a/TerranBASICexecutable/TerranBASICexecutable.iml
+++ b/TerranBASICexecutable/TerranBASICexecutable.iml
@@ -10,5 +10,7 @@
+
+
\ No newline at end of file
diff --git a/assets/disk0/tvdos/bin/playtad.js b/assets/disk0/tvdos/bin/playtad.js
index cd1bf3a..d43f076 100644
--- a/assets/disk0/tvdos/bin/playtad.js
+++ b/assets/disk0/tvdos/bin/playtad.js
@@ -1,7 +1,9 @@
const SND_BASE_ADDR = audio.getBaseAddr()
const SND_MEM_ADDR = audio.getMemAddr()
-const TAD_INPUT_ADDR = SND_MEM_ADDR - 262144 // TAD input buffer (matches TAV packet 0x24)
-const TAD_DECODED_ADDR = SND_MEM_ADDR - 262144 + 65536 // TAD decoded buffer
+// tadInputBin lives at audio-local offset 917504 and tadDecodedBin at 983040
+// (post-bef85f6 memory map; the old 262144 offset now hits the enlarged sampleBin).
+const TAD_INPUT_ADDR = SND_MEM_ADDR - 917504 // TAD input buffer (matches TAV packet 0x24)
+const TAD_DECODED_ADDR = SND_MEM_ADDR - 983040 // TAD decoded buffer
if (!SND_BASE_ADDR) return 10
diff --git a/assets/disk0/tvdos/bin/playtav.js b/assets/disk0/tvdos/bin/playtav.js
index a689f62..390d672 100644
--- a/assets/disk0/tvdos/bin/playtav.js
+++ b/assets/disk0/tvdos/bin/playtav.js
@@ -1746,7 +1746,9 @@ try {
tadInitialised = true
}
- seqread.readBytes(payloadLen, SND_MEM_ADDR - 262144)
+ // tadInputBin lives at audio-local offset 917504 (post-bef85f6 memory map);
+ // the previous 262144 offset now points into the enlarged sampleBin.
+ seqread.readBytes(payloadLen, SND_MEM_ADDR - 917504)
audio.tadDecode()
audio.tadUploadDecoded(AUDIO_DEVICE, sampleLen)
}
diff --git a/tsvm_core/src/net/torvald/tsvm/AudioJSR223Delegate.kt b/tsvm_core/src/net/torvald/tsvm/AudioJSR223Delegate.kt
index 1e4d613..fbe39b0 100644
--- a/tsvm_core/src/net/torvald/tsvm/AudioJSR223Delegate.kt
+++ b/tsvm_core/src/net/torvald/tsvm/AudioJSR223Delegate.kt
@@ -275,6 +275,7 @@ class AudioJSR223Delegate(private val vm: VM) {
+ // while the following code does work, it was decided that MP3 is "too new" for tsvm and thus removed.
/*
js-mp3
https://github.com/soundbus-technologies/js-mp3
diff --git a/tsvm_core/src/net/torvald/tsvm/GraphicsJSR223Delegate.kt b/tsvm_core/src/net/torvald/tsvm/GraphicsJSR223Delegate.kt
index 522cdf0..dac0497 100644
--- a/tsvm_core/src/net/torvald/tsvm/GraphicsJSR223Delegate.kt
+++ b/tsvm_core/src/net/torvald/tsvm/GraphicsJSR223Delegate.kt
@@ -5433,6 +5433,18 @@ class GraphicsJSR223Delegate(private val vm: VM) {
private val TAV_QLUT = intArrayOf(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,66,68,70,72,74,76,78,80,82,84,86,88,90,92,94,96,98,100,102,104,106,108,110,112,114,116,118,120,122,124,126,128,132,136,140,144,148,152,156,160,164,168,172,176,180,184,188,192,196,200,204,208,212,216,220,224,228,232,236,240,244,248,252,256,264,272,280,288,296,304,312,320,328,336,344,352,360,368,376,384,392,400,408,416,424,432,440,448,456,464,472,480,488,496,504,512,528,544,560,576,592,608,624,640,656,672,688,704,720,736,752,768,784,800,816,832,848,864,880,896,912,928,944,960,976,992,1008,1024,1056,1088,1120,1152,1184,1216,1248,1280,1312,1344,1376,1408,1440,1472,1504,1536,1568,1600,1632,1664,1696,1728,1760,1792,1824,1856,1888,1920,1952,1984,2016,2048,2112,2176,2240,2304,2368,2432,2496,2560,2624,2688,2752,2816,2880,2944,3008,3072,3136,3200,3264,3328,3392,3456,3520,3584,3648,3712,3776,3840,3904,3968,4032,4096)
+ // Zstd magic = 0x28 0xB5 0x2F 0xFD (little-endian frame magic).
+ // Newer TAV files default to no Zstd (Video Flags bit 4); detecting the magic
+ // lets the decoder accept both compressed and raw payloads transparently.
+ private fun tavDecompressIfZstd(data: ByteArray): ByteArray {
+ if (data.size >= 4 &&
+ data[0] == 0x28.toByte() && data[1] == 0xB5.toByte() &&
+ data[2] == 0x2F.toByte() && data[3] == 0xFD.toByte()) {
+ return ZstdInputStream(ByteArrayInputStream(data)).use { it.readBytes() }
+ }
+ return data
+ }
+
// New tavDecode function that accepts compressed data and decompresses internally
fun tavDecodeCompressed(compressedDataPtr: Long, compressedSize: Int, currentRGBAddr: Long, prevRGBAddr: Long,
width: Int, height: Int, qIndex: Int, qYGlobal: Int, qCoGlobal: Int, qCgGlobal: Int, channelLayout: Int,
@@ -5445,12 +5457,9 @@ class GraphicsJSR223Delegate(private val vm: VM) {
}
return try {
- // Decompress using Zstd
- val bais = ByteArrayInputStream(compressedData)
- val zis = ZstdInputStream(bais)
- val decompressedData = zis.readBytes()
- zis.close()
- bais.close()
+ // Decompress with Zstd if the payload starts with the Zstd frame magic;
+ // otherwise pass through (TAV files written without --zstd-level).
+ val decompressedData = tavDecompressIfZstd(compressedData)
// Allocate buffer for decompressed data
val decompressedBuffer = vm.malloc(decompressedData.size)
@@ -6725,9 +6734,9 @@ class GraphicsJSR223Delegate(private val vm: VM) {
)
val decompressedData = try {
- ZstdInputStream(java.io.ByteArrayInputStream(compressedData)).use { zstd ->
- zstd.readBytes()
- }
+ // Decompress with Zstd if the payload starts with the Zstd frame magic;
+ // otherwise pass through (TAV files written without --zstd-level).
+ tavDecompressIfZstd(compressedData)
} catch (e: Exception) {
println("ERROR: Zstd decompression failed: ${e.message}")
return arrayOf(0, dbgOut)
diff --git a/tsvm_core/src/net/torvald/tsvm/peripheral/AudioAdapter.kt b/tsvm_core/src/net/torvald/tsvm/peripheral/AudioAdapter.kt
index b4a34d2..e80a64f 100644
--- a/tsvm_core/src/net/torvald/tsvm/peripheral/AudioAdapter.kt
+++ b/tsvm_core/src/net/torvald/tsvm/peripheral/AudioAdapter.kt
@@ -911,24 +911,32 @@ class AudioAdapter(val vm: VM) : PeriBase(VM.PERITYPE_SOUND) {
((tadInputBin[offset++].toUint()) shl 8)
)
val maxIndex = tadInputBin[offset++].toUint()
- val payloadSize = (
+ val payloadSizeField = (
(tadInputBin[offset++].toUint()) or
((tadInputBin[offset++].toUint()) shl 8) or
((tadInputBin[offset++].toUint()) shl 16) or
((tadInputBin[offset++].toUint()) shl 24)
)
- // Decompress payload
+ // MSB of payload size = 1 means the payload is stored uncompressed (no Zstd).
+ val payloadIsRaw = (payloadSizeField and 0x80000000.toInt()) != 0
+ val payloadSize = payloadSizeField and 0x7FFFFFFF
+
+ // Read payload bytes
val compressed = ByteArray(payloadSize)
UnsafeHelper.memcpyRaw(null, tadInputBin.ptr + offset, compressed, UnsafeHelper.getArrayOffset(compressed), payloadSize.toLong())
- val payload: ByteArray = try {
- ZstdInputStream(ByteArrayInputStream(compressed)).use { zstd ->
- zstd.readBytes()
+ val payload: ByteArray = if (payloadIsRaw) {
+ compressed
+ } else {
+ try {
+ ZstdInputStream(ByteArrayInputStream(compressed)).use { zstd ->
+ zstd.readBytes()
+ }
+ } catch (e: Exception) {
+ println("ERROR: Zstd decompression failed: ${e.message}")
+ return
}
- } catch (e: Exception) {
- println("ERROR: Zstd decompression failed: ${e.message}")
- return
}
// Decode using binary tree EZBC - FIXED!
diff --git a/tsvm_core/tsvm_core.iml b/tsvm_core/tsvm_core.iml
index 516f4f1..45571f2 100644
--- a/tsvm_core/tsvm_core.iml
+++ b/tsvm_core/tsvm_core.iml
@@ -12,5 +12,7 @@
+
+
\ No newline at end of file
diff --git a/tsvm_executable.iml b/tsvm_executable.iml
index 942bf2b..b01ef5a 100644
--- a/tsvm_executable.iml
+++ b/tsvm_executable.iml
@@ -10,5 +10,7 @@
+
+
\ No newline at end of file
diff --git a/video_encoder/Makefile b/video_encoder/Makefile
deleted file mode 100644
index 413592e..0000000
--- a/video_encoder/Makefile
+++ /dev/null
@@ -1,221 +0,0 @@
-# Created by CuriousTorvald and Claude on 2025-08-17.
-# Makefile for TSVM Enhanced Video (TEV) encoder and libraries
-
-CC = gcc
-CXX = g++
-CFLAGS = -std=c99 -Wall -Wextra -Ofast -D_GNU_SOURCE -march=native -mavx512f -mavx512dq -mavx512bw -mavx512vl -Iinclude
-CXXFLAGS = -std=c++11 -Wall -Wextra -Ofast -D_GNU_SOURCE -march=native -mavx512f -mavx512dq -mavx512bw -mavx512vl -Iinclude
-DBGFLAGS =
-PREFIX = /usr/local
-
-# Zstd flags (use pkg-config if available, fallback for cross-platform compatibility)
-ZSTD_CFLAGS = $(shell pkg-config --cflags libzstd 2>/dev/null || echo "")
-ZSTD_LIBS = $(shell pkg-config --libs libzstd 2>/dev/null || echo "-lzstd")
-LIBS = -lm $(ZSTD_LIBS)
-
-# =============================================================================
-# Library Object Files
-# =============================================================================
-
-# libtavenc - TAV encoder library
-LIBTAVENC_OBJ = lib/libtavenc/tav_encoder_lib.o \
- lib/libtavenc/tav_encoder_color.o \
- lib/libtavenc/tav_encoder_dwt.o \
- lib/libtavenc/tav_encoder_quantize.o \
- lib/libtavenc/tav_encoder_ezbc.o \
- lib/libtavenc/tav_encoder_utils.o \
- lib/libtavenc/tav_encoder_tile.o
-
-# libtavdec - TAV decoder library
-LIBTAVDEC_OBJ = lib/libtavdec/tav_video_decoder.o
-
-# libtadenc - TAD encoder library
-LIBTADENC_OBJ = lib/libtadenc/encoder_tad.o
-
-# libtaddec - TAD decoder library
-LIBTADDEC_OBJ = lib/libtaddec/decoder_tad.o
-
-# libfec - Forward Error Correction library (LDPC + Reed-Solomon)
-LIBFEC_OBJ = lib/libfec/ldpc.o lib/libfec/reed_solomon.o lib/libfec/ldpc_payload.o
-
-# =============================================================================
-# Targets
-# =============================================================================
-
-# Source files and targets
-TARGETS = libs encoder_tav_ref decoder_tav_ref tav_inspector tad tav_dt
-LIBRARIES = lib/libtavenc.a lib/libtavdec.a lib/libtadenc.a lib/libtaddec.a lib/libfec.a
-TAV_TARGETS = encoder_tav_ref decoder_tav_ref tav_inspector
-TAD_TARGETS = encoder_tad decoder_tad
-DT_TARGETS = encoder_tav_dt decoder_tav_dt tavdt_noise_injector
-
-# Build all encoders (default)
-all: clean $(TARGETS)
-
-# Build all libraries
-libs: $(LIBRARIES)
-
-# Reference encoder using libtavenc (replaces old monolithic encoder)
-encoder_tav_ref: src/encoder_tav.c lib/libtavenc.a lib/libtadenc.a
- rm -f encoder_tav_ref
- $(CC) $(CFLAGS) $(ZSTD_CFLAGS) -Iinclude -o encoder_tav_ref src/encoder_tav.c lib/libtavenc.a lib/libtadenc.a $(LIBS)
- @echo ""
- @echo "Reference encoder built: encoder_tav_ref"
- @echo "This is the official reference implementation with all features"
-
-# Reference decoder using libtavdec (replaces old monolithic decoder)
-decoder_tav_ref: src/decoder_tav.c lib/libtavdec.a lib/libtaddec.a
- rm -f decoder_tav_ref
- $(CC) $(CFLAGS) $(ZSTD_CFLAGS) -Iinclude -o decoder_tav_ref src/decoder_tav.c lib/libtavdec.a lib/libtaddec.a $(LIBS)
- @echo ""
- @echo "Reference decoder built: decoder_tav_ref"
- @echo "This is the official reference implementation with all features"
-
-tav_inspector: tav_inspector.c lib/libfec.a
- rm -f tav_inspector
- $(CC) $(CFLAGS) $(ZSTD_CFLAGS) -Ilib/libfec -o tav_inspector $< lib/libfec.a $(LIBS)
-
-tav: $(TAV_TARGETS)
-
-# Build TAD (Terrarum Advanced Audio) tools
-encoder_tad: src/encoder_tad_standalone.c lib/libtadenc/encoder_tad.c include/encoder_tad.h
- rm -f encoder_tad encoder_tad_standalone.o encoder_tad.o
- $(CC) $(CFLAGS) $(ZSTD_CFLAGS) -c lib/libtadenc/encoder_tad.c -o encoder_tad.o
- $(CC) $(CFLAGS) $(ZSTD_CFLAGS) -c src/encoder_tad_standalone.c -o encoder_tad_standalone.o
- $(CC) $(DBGFLAGS) -o encoder_tad encoder_tad_standalone.o encoder_tad.o $(LIBS)
-
-decoder_tad: lib/libtaddec/decoder_tad.c
- rm -f decoder_tad
- $(CC) $(CFLAGS) $(ZSTD_CFLAGS) -o decoder_tad $< $(LIBS)
-
-# Build all TAD tools
-tad: $(TAD_TARGETS)
-
-# =============================================================================
-# Library Build Rules
-# =============================================================================
-
-# Compile library object files
-lib/libtavenc/%.o: lib/libtavenc/%.c
- $(CC) $(CFLAGS) $(ZSTD_CFLAGS) -c $< -o $@
-
-lib/libtavdec/%.o: lib/libtavdec/%.c
- $(CC) $(CFLAGS) $(ZSTD_CFLAGS) -c $< -o $@
-
-lib/libtadenc/%.o: lib/libtadenc/%.c
- $(CC) $(CFLAGS) $(ZSTD_CFLAGS) -c $< -o $@
-
-lib/libtaddec/%.o: lib/libtaddec/%.c
- $(CC) $(CFLAGS) $(ZSTD_CFLAGS) -DTAD_DECODER_LIB -c $< -o $@
-
-lib/libfec/%.o: lib/libfec/%.c
- $(CC) $(CFLAGS) -Ilib/libfec -c $< -o $@
-
-# Build static libraries
-lib/libtavenc.a: $(LIBTAVENC_OBJ)
- ar rcs $@ $^
-
-lib/libtavdec.a: $(LIBTAVDEC_OBJ)
- ar rcs $@ $^
-
-lib/libtadenc.a: $(LIBTADENC_OBJ)
- ar rcs $@ $^
-
-lib/libtaddec.a: $(LIBTADDEC_OBJ)
- ar rcs $@ $^
-
-lib/libfec.a: $(LIBFEC_OBJ)
- ar rcs $@ $^
-
-# =============================================================================
-# TAV-DT (Digital Tape) Encoder/Decoder
-# =============================================================================
-
-# TAV-DT encoder with FEC (multithreaded)
-encoder_tav_dt: src/encoder_tav_dt.c lib/libtavenc.a lib/libtadenc.a lib/libfec.a
- rm -f encoder_tav_dt
- $(CC) $(CFLAGS) $(ZSTD_CFLAGS) -Iinclude -Ilib/libfec -o encoder_tav_dt src/encoder_tav_dt.c lib/libtavenc.a lib/libtadenc.a lib/libfec.a $(LIBS) -lpthread
- @echo ""
- @echo "TAV-DT encoder built: encoder_tav_dt"
- @echo "Digital Tape format with LDPC and Reed-Solomon FEC (multithreaded)"
-
-# TAV-DT decoder with FEC (multithreaded)
-decoder_tav_dt: src/decoder_tav_dt.c lib/libtavdec.a lib/libtaddec.a lib/libfec.a
- rm -f decoder_tav_dt
- $(CC) $(CFLAGS) $(ZSTD_CFLAGS) -Iinclude -Ilib/libfec -o decoder_tav_dt src/decoder_tav_dt.c lib/libtavdec.a lib/libtaddec.a lib/libfec.a $(LIBS) -lpthread
- @echo ""
- @echo "TAV-DT decoder built: decoder_tav_dt"
- @echo "Digital Tape format with LDPC and Reed-Solomon FEC (multithreaded)"
-
-# TAV-DT noise injector (channel simulator)
-tavdt_noise_injector: tavdt_noise_injector.c
- rm -f tavdt_noise_injector
- $(CC) -std=c99 -Wall -Ofast -D_GNU_SOURCE -o tavdt_noise_injector tavdt_noise_injector.c -lm
- @echo ""
- @echo "TAV-DT noise injector built: tavdt_noise_injector"
- @echo "Simulates QPSK satellite channel noise (AWGN + burst)"
-
-# Build all TAV-DT tools
-tav_dt: $(DT_TARGETS)
-
-# Build with debug symbols
-debug: CFLAGS += -g -DDEBUG -fsanitize=address -fno-omit-frame-pointer
-debug: DBGFLAGS += -fsanitize=address -fno-omit-frame-pointer
-debug: clean $(TARGETS)
-
-# Clean build artifacts
-clean:
- rm -f $(TARGETS) $(TAD_TARGETS) $(DT_TARGETS) $(LIBRARIES) *.o lib/*/*.o
-
-# Install (copy to PATH)
-install: $(TARGETS)
- cp encoder_tav_ref $(PREFIX)/bin/
- cp decoder_tav_ref $(PREFIX)/bin/
- cp encoder_tad $(PREFIX)/bin/
- cp decoder_tad $(PREFIX)/bin/
- cp encoder_tav_dt $(PREFIX)/bin/
- cp decoder_tav_dt $(PREFIX)/bin/
- cp tav_inspector $(PREFIX)/bin/
-
-# Check for required dependencies
-check-deps:
- @echo "Checking dependencies..."
- @pkg-config --exists libzstd || (echo "Error: libzstd-dev not found. Install libzstd-dev or equivalent" && exit 1)
- @echo "All dependencies found."
-
-# Help
-help:
- @echo "TSVM Advanced Video (TAV) and Audio (TAD) Encoders"
- @echo ""
- @echo "Targets:"
- @echo " all - Build video encoders (default)"
- @echo " libs - Build all codec libraries (.a files)"
- @echo " tav - Build the TAV advanced video encoder"
- @echo " tav_dt - Build all TAV-DT (Digital Tape) tools with FEC"
- @echo " tavdt_noise_injector - Build TAV-DT channel noise simulator"
- @echo " tad - Build all TAD audio tools (encoder, decoder)"
- @echo " encoder_tad - Build TAD audio encoder"
- @echo " decoder_tad - Build TAD audio decoder"
- @echo " tests - Build test programs"
- @echo " debug - Build with debug symbols"
- @echo " clean - Remove build artifacts"
- @echo " install - Install to /usr/local/bin"
- @echo " check-deps - Check for required dependencies"
- @echo " help - Show this help"
- @echo ""
- @echo "Libraries:"
- @echo " lib/libtavenc.a - TAV encoder library"
- @echo " lib/libtavdec.a - TAV decoder library"
- @echo " lib/libtadenc.a - TAD encoder library"
- @echo " lib/libtaddec.a - TAD decoder library"
- @echo " lib/libfec.a - Forward Error Correction library (LDPC + RS)"
- @echo ""
- @echo "Usage:"
- @echo " make # Build video encoders"
- @echo " make libs # Build all libraries"
- @echo " make tav # Build TAV encoder"
- @echo " make tav_dt # Build TAV-DT encoder/decoder with FEC"
- @echo " make tad # Build all TAD audio tools"
- @echo " sudo make install # Install all encoders"
-
-.PHONY: all libs clean install check-deps help debug tad tav_dt tests
diff --git a/video_encoder/TAD_README.md b/video_encoder/TAD_README.md
deleted file mode 100644
index 81be478..0000000
--- a/video_encoder/TAD_README.md
+++ /dev/null
@@ -1,350 +0,0 @@
-# TAD - TSVM Advanced Audio Codec
-
-A perceptually-optimised wavelet-based audio codec designed for resource-constrained systems, featuring CDF 9/7 wavelets, EZBC sparse coding, and sophisticated perceptual quantisation.
-
-## Overview
-
-TAD (TSVM Advanced Audio) is a modern audio codec built on discrete wavelet transform (DWT) using Cohen-Daubechies-Feauveau (CDF) 9/7 biorthogonal wavelets. It combines perceptual quantisation, advanced entropy coding, and careful optimisation for resource-constrained systems.
-
-### Key Advantages
-
-- **Perceptual optimisation**: HVS-aware quantisation preserves audio quality where it matters
-- **Efficient sparse coding**: EZBC encoding exploits coefficient sparsity (86.9% zeros in typical content)
-- **Variable chunk sizes**: Supports any chunk size ≥1024 samples, including non-power-of-2
-- **Stereo decorrelation**: Mid/Side encoding exploits stereo correlation for better compression
-- **Hardware-friendly**: Designed for efficient decoding on resource-constrained platforms
-
-## Features
-
-### Compression Technology
-
-- **CDF 9/7 Biorthogonal Wavelets**
- - 9-level fixed decomposition for all chunk sizes
- - Lifting scheme implementation for efficient computation
- - Optimal frequency discrimination for audio signals
-
-- **Pre-processing**
- - First-order IIR pre-emphasis filter (α=0.5) shifts quantisation noise to lower frequencies, where they are less objectionable to listeners
- - Gamma companding (γ=0.5) for dynamic range compression before quantisation
- - Mid/Side stereo transformation exploits stereo correlation
- - Lambda companding (λ=6.0) with Laplacian CDF mapping for full bit utilisation
-
-- **Perceptual Quantisation**
- - Channel-specific (Mid/Side) frequency-dependent weights
- - Subband-aware quantisation preserves perceptually important frequencies
-
-- **EZBC Encoding**
- - Binary tree embedded zero block coding
- - Exploits coefficient sparsity (86.9% Mid, 97.8% Side typical)
- - Progressive refinement structure
- - Spatial clustering of non-zero coefficients
-
-- **Entropy Coding**
- - Zstandard compression (level 7) on concatenated EZBC bitstreams
- - Cross-channel compression optimisation
- - Optional Zstd bypass for debugging
-
-### Audio Format
-
-- **Sample Rate**: 32 KHz (TSVM audio hardware native format)
-- **Channels**: Stereo (L/R input, Mid/Side internal representation)
-- **Chunk Sizes**: Variable, any size ≥1024 samples (including non-power-of-2)
-- **Bit Depth**: 32-bit float internal, 8-bit unsigned PCM output with noise-shaped dithering
-- **Bandwidth**: Full 0-16 KHz frequency range preserved
-
-### Quality Levels
-
-Six quality levels (0-5) provide a wide range of compression/quality trade-offs:
-- **Level 0**: Lowest quality, smallest file size
-- **Level 3**: Default, balanced quality/compression (2.51:1 vs PCMu8)
-- **Level 5**: Highest quality, largest file size
-
-Quality levels are designed to be synchronised with TAV video codec for unified encoding.
-
-## Building
-
-### Prerequisites
-
-- C compiler (GCC/Clang)
-- Zstandard library (libzstd)
-- Math library (libm)
-
-### Compilation
-
-```bash
-# Build TAD encoder/decoder
-make tad
-
-# Build all tools
-make all
-
-# Clean build artifacts
-make clean
-```
-
-### Build Targets
-
-- `encoder_tad` - Standalone audio encoder with FFmpeg calls
-- `decoder_tad` - Standalone audio decoder
-
-## Usage
-
-### Basic Encoding
-
-Encoding requires FFmpeg executable installed in your system.
-
-```bash
-# Default encoding (quality level 3)
-./encoder_tad -i input.mp3 -o output.tad
-
-# Specify quality level (0-5)
-./encoder_tad -i input.m4a -o output.tad -q 0 # Lowest quality
-./encoder_tad -i input.ogg -o output.tad -q 5 # Highest quality
-
-# Disable Zstd compression (for debugging)
-./encoder_tad -i input.opus -o output.tad --no-zstd
-
-# Verbose output with statistics
-./encoder_tad -i input.flac -o output.tad -v
-```
-
-### Decoding
-
-```bash
-# Decode to PCMu8
-./decoder_tad -i input.tad -o output.pcm --raw-pcm
-
-# Decode to WAV
-./decoder_tad -i input.tad -o output.wav
-```
-
-### Input Formats
-
-TAD encoder accepts any audio format supported by FFmpeg:
-- Audio files: WAV, MP3, FLAC, OGG, AAC, etc.
-- Video files with audio streams: MP4, MKV, AVI, etc.
-- Raw PCM formats
-
-Audio is automatically resampled to 32 KHz stereo if necessary.
-
-## Technical Architecture
-
-### Encoder Pipeline
-
-1. **Input Processing**
- - FFmpeg demuxing and audio stream extraction
- - Resampling to 32 KHz stereo
- - Conversion to PCM32f
-
-2. **Pre-emphasis Filter**
- - First-order IIR filter with α=0.5
- - Shifts quantisation noise toward lower frequencies
- - Improves perceptual quality
-
-3. **Gamma Companding**
- - Dynamic range compression with γ=0.5
- - Applied independently to each sample
- - Reduces quantisation error for low-amplitude signals
-
-4. **Stereo Decorrelation**
- - Left/Right to Mid/Side transformation
- - Mid = (L + R) / 2
- - Side = (L - R) / 2
- - Exploits stereo correlation for better compression
-
-5. **9-Level CDF 9/7 DWT**
- - Fixed 9 decomposition levels for all chunk sizes
- - Forward lifting scheme implementation
- - Correct length tracking for non-power-of-2 sizes
-
-6. **Perceptual Quantisation**
- - Channel-specific (Mid/Side) subband weights
- - Lambda companding with λ=6.0
- - Laplacian CDF mapping: `sign(x) * floor(λ * log(1 + |x|/λ))`
- - Quantised to int8 coefficients
-
-7. **EZBC Encoding**
- - Binary tree structure per channel
- - Progressive refinement by bitplanes
- - Zero block coding exploits sparsity
- - Independent bitstreams for Mid and Side
-
-8. **Zstd Compression**
- - Level 7 compression on concatenated `[Mid_bitstream][Side_bitstream]`
- - Cross-channel optimisation opportunities
- - Adaptive compression based on content
-
-### Decoder Pipeline
-
-1. **Container Parsing**
- - TAD packet identification (type 0x24)
- - Chunk size extraction
- - Compressed data boundaries
-
-2. **Zstd Decompression**
- - Decompress concatenated bitstreams
- - Split into Mid and Side EZBC streams
-
-3. **EZBC Decoding**
- - Binary tree decoder per channel
- - Reconstruct quantised int8 coefficients
- - Progressive refinement reconstruction
-
-4. **Lambda Decompanding**
- - Inverse Laplacian CDF with channel-specific weights
- - Reconstruct float32 DWT coefficients
- - Apply subband-specific perceptual weights
-
-5. **9-Level Inverse CDF 9/7 DWT**
- - Inverse lifting scheme implementation
- - Correct length tracking for non-power-of-2 chunk sizes
- - Pre-calculated length sequence from forward transform
-
-6. **Mid/Side to Left/Right**
- - L = Mid + Side
- - R = Mid - Side
- - Reconstruct stereo channels
-
-7. **Gamma Decompanding**
- - Inverse gamma with γ⁻¹=2.0
- - Restore original dynamic range
-
-8. **De-emphasis Filter**
- - Reverse pre-emphasis with α=0.5
- - Remove frequency shaping
- - Restore flat frequency response
-
-9. **PCM32f to PCM8u Conversion**
- - Noise-shaped dithering for 8-bit output
- - Clamping to valid range
- - Final output format
-
-### Wavelet Implementation
-
-CDF 9/7 wavelet follows a **two-stage lifting scheme**:
-
-```c
-// Forward Transform: Predict → Update
-// Predict step (generate high-pass)
-temp[half + i] = data[odd] - α * (data[even_left] + data[even_right]);
-
-// Update step (generate low-pass)
-temp[i] = data[even] + β * (temp[half + i - 1] + temp[half + i]);
-
-// Normalization (K factor)
-temp[i] *= K;
-temp[half + i] /= K;
-
-// Inverse Transform: Denormalize → Undo Update → Undo Predict (reversed order)
-temp[i] /= K;
-temp[half + i] *= K;
-
-temp[i] -= β * (temp[half + i - 1] + temp[half + i]);
-data[odd] = temp[half + i] + α * (temp[i] + temp[i + 1]);
-data[even] = temp[i];
-```
-
-**CDF 9/7 Coefficients**:
-- α = -1.586134342
-- β = -0.052980118
-- γ = +0.882911075
-- δ = +0.443506852
-- K = 1.230174105
-
-### Non-Power-of-2 Chunk Size Handling
-
-Critical implementation detail for variable chunk sizes:
-
-```c
-// Pre-calculate exact length sequence from forward transform
-int lengths[MAX_LEVELS + 1];
-lengths[0] = chunk_size;
-for (int i = 1; i <= levels; i++) {
- lengths[i] = (lengths[i - 1] + 1) / 2;
-}
-
-// Apply inverse DWT using lengths[level] for each level
-// NEVER use simple doubling (length *= 2) - incorrect for non-power-of-2!
-```
-
-Incorrect length tracking causes mirrored subband artefacts in decoded audio.
-
-### Perceptual Quantisation Weights
-
-Channel-specific weights for Mid (channel 0) and Side (channel 1):
-
-```c
-// Base quantiser weights per subband (9 levels + approximation)
-float BASE_QUANTISER_WEIGHTS[2][10] = {
- // Mid channel (0)
- {4.0f, 2.0f, 1.8f, 1.6f, 1.4f, 1.2f, 1.0f, 1.0f, 1.3f, 2.0f},
-
- // Side channel (1)
- {6.0f, 5.0f, 2.6f, 2.4f, 1.8f, 1.3f, 1.0f, 1.0f, 1.6f, 3.2f}
-};
-
-// During dequantisation:
-float weight = BASE_QUANTISER_WEIGHTS[channel][subband] * quantiser_scale;
-coeffs[i] = normalised_val * TAD32_COEFF_SCALARS[subband] * weight;
-```
-
-Different weights for Mid and Side channels reflect perceptual importance of frequency bands in each channel. DC frequency has highest weight (4.0 Mid, 6.0 Side) due to energy concentration.
-
-## Performance Characteristics
-
-### Compression Efficiency
-
-- **Target Compression**: 2:1 against PCMu8 baseline (4:1 against PCM16LE input)
-- **Achieved Compression**: 2.51:1 against PCMu8 at quality level 3
-- **Audio Quality**: Preserves full 0-16 KHz bandwidth
-- **Coefficient Sparsity**: 86.9% zeros in Mid channel, 97.8% in Side channel (typical)
-- **EZBC Benefits**: Exploits sparsity, progressive refinement, spatial clustering
-
-### Computational Complexity
-
-- **Encoding**: O(n log n) per chunk for DWT, O(n) for EZBC encoding
-- **Decoding**: O(n log n) per chunk for inverse DWT, O(n) for EZBC decoding
-- **Memory**: O(n) working memory for chunk processing
-
-### Quality Characteristics
-
-- **Frequency Response**: Flat 0-16 KHz within perceptual limits
-- **Dynamic Range**: Preserved through gamma companding
-- **Stereo Imaging**: Maintained through Mid/Side decorrelation
-- **Perceptual Quality**: Optimised for human auditory system characteristics
-
-## Integration with TAV
-
-TAD is designed as an includable API for TAV video encoder integration:
-
-- **Variable Chunk Sizes**: Audio chunks can match video GOP boundaries (e.g., 32016 samples for 1-second TAV GOP)
-- **Unified Quality Levels**: TAD quality 0-5 synchronised with TAV quality 0-5
-- **Embedded Packets**: TAV embeds TAD-compressed audio using packet type 0x24
-- **Shared Container**: Single .tav file contains both video and audio streams
-
-### TAV Integration Example
-
-```c
-// TAD handles non-power-of-2 chunk size correctly
-tad_encode_chunk(audio_buffer, audio_samples_per_gop, output_buffer, &output_size);
-
-// TAV embeds TAD packet
-tav_write_packet(TAV_PACKET_AUDIO, output_buffer, output_size);
-```
-
-## Format Specification
-
-For complete packet structure and bitstream format details, refer to `format documentation.txt`.
-
-### Key Packet Types
-
-- `0x24`: TAD audio packet (used in standalone .tad files and embedded in .tav files)
-
-## Related Projects
-
-- **TAV** (TSVM Advanced Video): Wavelet-based video codec with integrated TAD audio
-- **TSVM**: Target virtual machine platform for TAD playback
-
-## Licence
-
-MIT.
diff --git a/video_encoder/TAV_README.md b/video_encoder/TAV_README.md
deleted file mode 100644
index 5d003cc..0000000
--- a/video_encoder/TAV_README.md
+++ /dev/null
@@ -1,261 +0,0 @@
-# TAV - TSVM Advanced Video Codec
-
-A perceptually-optimised wavelet-based video codec designed for resource-constrained systems, featuring multiple wavelet types, temporal 3D DWT, and sophisticated compression techniques.
-
-## Overview
-
-TAV (TSVM Advanced Video) is a modern video codec built on discrete wavelet transformation (DWT). It combines cutting-edge compression techniques with careful optimisation for resource-constrained systems.
-
-### Key Advantages
-
-- **No blocking artefacts**: Large-tile DWT encoding with padding eliminates DCT block boundaries
-- **No colour banding**: Wavelets spreads gradients across scales, preventing banding in the first place
-- **Perceptual optimisation**: HVS-aware quantisation preserves visual quality where it matters
-- **Temporal coherence**: 3D DWT with GOP encoding exploits inter-frame similarity
-- **Efficient sparse coding**: EZBC encoding exploits coefficient sparsity for 16-18% additional compression
-- **Hardware-friendly**: Designed for efficient decoding on resource-constrained platforms
-
-## Features
-
-### Compression Technology
-
-- **Wavelet Types**
- - **5/3 Reversible** (JPEG 2000 standard): Lossless-capable, good for archival
- - **9/7 Irreversible** (default): Best overall compression, CDF 9/7 variant
-
-- **Spatial Encoding**
- - Large-tile encoding with padding, with optional single-tile mode (no blocking artefacts)
- - 6-level DWT decomposition for deep frequency analysis
- - Perceptual quantisation with HVS-optimised coefficient scaling
- - YCoCg-R colour space with anisotropic chroma quantisation
-
-- **Temporal Encoding** (3D DWT Mode)
- - Group-of-pictures (GOP) encoding with adaptive size (typically 20 frames)
- - Unified EZBC encoding across temporal dimension
- - Adaptive GOP boundaries with scene change detection
-
-- **EZBC Encoding**
- - Binary tree embedded zero block coding exploits coefficient sparsity
- - Progressive refinement structure with bitplane encoding
- - Concatenated channel layout for cross-channel compression optimisation
- - Typical sparsity: 86.9% (Y), 97.8% (Co), 99.5% (Cg)
- - 16-18% compression improvement over naive coefficient encoding
-
-### Audio Integration
-
-TAV seamlessly integrates with the TAD (TSVM Advanced Audio) codec for synchronised audio/video encoding:
-- Variable chunk sizes match video GOP boundaries
-- Embedded TAD packets (type 0x24) with Zstd compression
-- Unified container format
-
-## Building
-
-### Prerequisites
-
-- C compiler (GCC/Clang)
-- Zstandard library
-- OpenCV 4 library (only used by experimental motion estimation feature)
-
-### Compilation
-
-```bash
-# Build TAV encoder/decoder
-make tav
-
-# Build all tools including TAD audio codec
-make all
-
-# Clean build artefacts
-make clean
-```
-
-### Build Targets
-
-- `encoder_tav` - Main video encoder
-- `decoder_tav` - Standalone video decoder
-- `tav_inspector` - Packet analysis and debugging tool
-
-## Usage
-
-### Basic Encoding
-
-Encoding requires FFmpeg executable installed in your system.
-
-```bash
-# Default encoding (CDF 9/7 wavelet, quality level 3)
-./encoder_tav -i input.mp4 -o output.tav
-
-# Quality levels (0-5)
-./encoder_tav -i input.avi -q 0 -o output.tav # Lowest quality, smallest file
-./encoder_tav -i input.mkv -q 5 -o output.tav # Highest quality, largest file
-```
-
-### Intra-only Encoding
-
-```bash
-# Enable Intra-only encoding
-./encoder_tav -i input.mp4 --intra-only -o output.tav
-```
-
-### Decoding and Inspection
-
-```bash
-# Decode TAV to raw video
-./decoder_tav -i input.tav -o output.mkv
-
-# Inspect packet structure (debugging)
-./tav_inspector input.tav -v
-```
-
-### Frame Limiting
-
-```bash
-# Encode only first N frames (useful for testing)
-./encoder_tav -i input.mp4 -o output.tav --encode-limit 100
-```
-
-## Technical Architecture
-
-### Encoder Pipeline
-
-1. **Input Processing**
- - FFmpeg demuxing and frame extraction
- - RGB to YCoCg-R colour space conversion
- - Resolution validation and padding
-
-2. **DWT Transform**
- - Spatial: 6-level decomposition per frame
- - Temporal: 1D DWT across GOP frames (3D DWT mode)
- - Lifting scheme implementation for all wavelets
-
-3. **Perceptual Quantisation**
- - HVS-based subband weights
- - Anisotropic chroma quantisation (YCoCg-R specific)
- - Quality-dependent quantisation matrices
-
-4. **EZBC Encoding**
- - Binary tree embedded zero block coding per channel
- - Progressive refinement by bitplanes
- - Concatenated bitstream layout: `[Y_bitstream][Co_bitstream][Cg_bitstream]`
- - Cross-channel compression optimisation
-
-5. **Entropy Coding**
- - Zstandard compression (level 7) on concatenated EZBC bitstreams
- - Cross-channel compression opportunities
- - Adaptive compression based on GOP structure
-
-### Decoder Pipeline
-
-1. **Container Parsing**
- - Packet type identification (0x00-0xFF)
- - Timecode synchronisation
- - GOP boundary detection
-
-2. **Entropy Decoding**
- - Zstd decompression of concatenated bitstreams
- - EZBC binary tree decoding per channel
- - Progressive coefficient reconstruction
-
-3. **Inverse Quantisation**
- - Perceptual weight application
- - Subband-specific scaling
- - Coefficient reconstruction from sparse representation
-
-4. **Inverse DWT**
- - Temporal: 1D inverse DWT across frames (3D DWT mode)
- - Spatial: 6-level inverse wavelet reconstruction
-
-5. **Output Conversion**
- - YCoCg-R to RGB colour space
- - Clamping and dithering
- - Frame buffering for display
-
-### Wavelet Implementation
-
-All wavelets follow a **lifting scheme** pattern with symmetric boundary extension:
-
-```c
-// Forward Transform: Predict → Update
-temp[half + i] = data[odd] - predict(data[even]); // High-pass
-temp[i] = data[even] + update(temp[half]); // Low-pass
-
-// Inverse Transform: Undo Update → Undo Predict (reversed order)
-data[even] = temp[i] - update(temp[half]); // Undo low-pass
-data[odd] = temp[half + i] + predict(data[even]); // Undo high-pass
-```
-
-**Critical**: Forward and inverse transforms must use identical coefficient indexing and exactly reverse operations to avoid grid artefacts.
-
-### Coefficient Layout
-
-TAV uses **2D Spatial Layout** in memory for each decomposition level:
-
-```
-[LL] [LH] [HL] [HH] [LH] [HL] [HH] ...
- └── Level 0 ──┘ └─── Level 1 ───┘
-```
-
-- `LL`: Low-pass (approximation) - progressively smaller with each level
-- `LH`, `HL`, `HH`: High-pass subbands (horizontal, vertical, diagonal detail)
-
-## Performance Characteristics
-
-### Compression Efficiency
-
-- **Sparsity Exploitation**: Typical quantised coefficient sparsity
- - Y channel: 86.9% zeros
- - Co channel: 97.8% zeros
- - Cg channel: 99.5% zeros
-
-- **EZBC Benefits**: 16-18% compression improvement over naive coefficient encoding through sparsity exploitation
-
-- **Temporal Coherence**: Additional 15-25% improvement with 3D DWT (content-dependent)
-
-### Computational Complexity
-
-- **Encoding**: O(n log n) per frame for spatial DWT
-- **Decoding**: O(n log n) per frame, optimised lifting scheme implementation
-- **Memory**: Single-tile encoding requires O(w × h) working memory
-
-### Quality Characteristics
-
-- **No blocking artefacts**: Wavelet-based encoding is inherently smooth
-- **Perceptual optimisation**: Better subjective quality than bitrate-equivalent DCT codecs
-- **Scalability**: 6 quality levels (0-5) provide wide range of bitrate/quality trade-offs
-- **Temporal stability**: 3D DWT mode reduces flickering and temporal artefacts
-
-## Format Specification
-
-For complete packet structure and bitstream format details, refer to `format documentation.txt`.
-
-### Key Packet Types
-
-- `0x00`: Metadata and initialisation
-- `0x01`: I-frame (intra-coded frame)
-- `0x12`: GOP unified packet (3D DWT mode)
-- `0x24`: Embedded TAD audio
-- `0xFC`: GOP synchronisation
-- `0xFD`: Timecode
-
-## Debugging Tools
-
-### TAV Inspector
-
-Analyse TAV packet structure and decode individual frames:
-
-```bash
-# Verbose packet analysis
-./tav_inspector input.tav -v
-
-# Extract specific frame ranges
-./tav_inspector input.tav --frame-range 100-200
-```
-
-## Related Projects
-
-- **TAD** (TSVM Advanced Audio): Perceptual audio codec using CDF 9/7 wavelets
-- **TSVM**: Target virtual machine platform for TAV playback
-
-## Licence
-
-MIT.
diff --git a/video_encoder/create_ucf_payload.c b/video_encoder/create_ucf_payload.c
deleted file mode 100644
index c0a2e34..0000000
--- a/video_encoder/create_ucf_payload.c
+++ /dev/null
@@ -1,424 +0,0 @@
-/**
- * TAV+UCF Payload Writer for TAV Files
- * Creates a TAV header-only (32 bytes) + UCF cue file (4KB) for concatenated TAV files
- * Total output size: 4096 bytes (32 + 4064)
- * Usage: ./create_ucf_payload input.tav output.ucf [track_names.txt]
- */
-
-#include
-#include
-#include
-#include
-
-#define TAV_HEADER_SIZE 32
-#define UCF_SIZE 4064
-#define TAV_OFFSET_BIAS (TAV_HEADER_SIZE + UCF_SIZE)
-#define TAV_MAGIC "\x1FTSVMTA" // Matches both TAV and TAP
-
-typedef struct {
- uint8_t magic[8];
- uint8_t version;
- uint16_t width;
- uint16_t height;
- uint8_t fps;
- uint32_t total_frames;
- // ... rest of header fields
-} __attribute__((packed)) TAVHeader;
-
-// Write TAV header-only payload (File Role = 1)
-static void write_tav_header_only(FILE *out) {
- uint8_t header[TAV_HEADER_SIZE] = {0};
-
- // Magic: "\x1FTSVMTAV"
- header[0] = 0x1F;
- header[1] = 'T';
- header[2] = 'S';
- header[3] = 'V';
- header[4] = 'M';
- header[5] = 'T';
- header[6] = 'A';
- header[7] = 'V';
-
- // Version: 5 (YCoCg-R perceptual)
- header[8] = 5;
-
- // Width: 560 (little-endian)
- header[9] = 0x30;
- header[10] = 0x02;
-
- // Height: 448 (little-endian)
- header[11] = 0xC0;
- header[12] = 0x01;
-
- // FPS: 30
- header[13] = 30;
-
- // Total Frames: 0xFFFFFFFF (still image marker / not applicable)
- header[14] = 0xFF;
- header[15] = 0xFF;
- header[16] = 0xFF;
- header[17] = 0xFF;
-
- // Wavelet Filter Type: 1 (9/7 irreversible, default)
- header[18] = 1;
-
- // Decomposition Levels: 6
- header[19] = 6;
-
- // Quantiser Indices (Y, Co, Cg): 255 (not applicable for header-only)
- header[20] = 0xFF;
- header[21] = 0xFF;
- header[22] = 0xFF;
-
- // Extra Feature Flags: 0x80 (bit 7 = has no actual packets)
- header[23] = 0x80;
-
- // Video Flags: 0
- header[24] = 0;
-
- // Encoder quality level: 0
- header[25] = 0;
-
- // Channel layout: 0 (Y-Co-Cg)
- header[26] = 0;
-
- // Reserved[4]: zeros (27-30 already initialised to 0)
-
- // File Role: 1 (header-only, UCF payload follows)
- header[31] = 1;
-
- fwrite(header, 1, TAV_HEADER_SIZE, out);
-}
-
-// Write UCF header
-static void write_ucf_header(FILE *out, uint16_t num_cues) {
- uint8_t magic[8] = {0x1F, 'T', 'S', 'V', 'M', 'U', 'C', 'F'};
- uint8_t version = 1;
- uint32_t cue_file_size = TAV_OFFSET_BIAS;
- uint8_t reserved = 0;
-
- fwrite(magic, 1, 8, out);
- fwrite(&version, 1, 1, out);
- fwrite(&num_cues, 2, 1, out);
- fwrite(&cue_file_size, 4, 1, out);
- fwrite(&reserved, 1, 1, out);
-}
-
-// Write UCF cue element (internal addressing, human+machine interactable)
-static void write_cue_element(FILE *out, uint64_t offset, const char *name) {
- uint8_t addressing_mode = 0x22; // 0x20 (human) | 0x01 (machine) | 0x02 (internal)
- uint16_t name_len = strlen(name);
-
- // Offset with 4KB bias
- uint64_t biased_offset = offset + TAV_OFFSET_BIAS;
-
- fwrite(&addressing_mode, 1, 1, out);
- fwrite(&name_len, 2, 1, out);
- fwrite(name, 1, name_len, out);
-
- // Write 48-bit (6-byte) offset
- fwrite(&biased_offset, 6, 1, out);
-}
-
-// Read track names from file (newline-delimited)
-static char **read_track_names(const char *filename, int *count_out) {
- FILE *f = fopen(filename, "r");
- if (!f) {
- return NULL;
- }
-
- char **names = NULL;
- int count = 0;
- int capacity = 16;
- char line[256];
-
- names = malloc(capacity * sizeof(char *));
- if (!names) {
- fclose(f);
- return NULL;
- }
-
- while (fgets(line, sizeof(line), f)) {
- // Remove trailing newline
- size_t len = strlen(line);
- if (len > 0 && line[len - 1] == '\n') {
- line[len - 1] = '\0';
- len--;
- }
- if (len > 0 && line[len - 1] == '\r') {
- line[len - 1] = '\0';
- len--;
- }
-
- // Skip empty lines
- if (len == 0) {
- continue;
- }
-
- // Expand capacity if needed
- if (count >= capacity) {
- capacity *= 2;
- char **new_names = realloc(names, capacity * sizeof(char *));
- if (!new_names) {
- // Cleanup on failure
- for (int i = 0; i < count; i++) {
- free(names[i]);
- }
- free(names);
- fclose(f);
- return NULL;
- }
- names = new_names;
- }
-
- // Allocate and copy name
- names[count] = strdup(line);
- if (!names[count]) {
- // Cleanup on failure
- for (int i = 0; i < count; i++) {
- free(names[i]);
- }
- free(names);
- fclose(f);
- return NULL;
- }
- count++;
- }
-
- fclose(f);
- *count_out = count;
- return names;
-}
-
-// Find all TAV headers in the file (with smart packet-wise skipping)
-static int find_tav_headers(FILE *in, uint64_t **offsets_out) {
- uint64_t *offsets = NULL;
- int count = 0;
- int capacity = 16;
-
- offsets = malloc(capacity * sizeof(uint64_t));
- if (!offsets) {
- fprintf(stderr, "Error: Memory allocation failed\n");
- return -1;
- }
-
- // Seek to beginning
- fseek(in, 0, SEEK_SET);
-
- uint8_t magic[8];
-
- while (1) {
- // Remember current position before reading
- uint64_t pos = ftell(in);
-
- // Try to read magic
- if (fread(magic, 1, 8, in) != 8) {
- // End of file
- break;
- }
-
- // Check for TAV magic signature
- if (memcmp(magic, TAV_MAGIC, 7) == 0 && (magic[7] == 'V' || magic[7] == 'P')) {
- // Found TAV header
- if (count >= capacity) {
- capacity *= 2;
- uint64_t *new_offsets = realloc(offsets, capacity * sizeof(uint64_t));
- if (!new_offsets) {
- fprintf(stderr, "Error: Memory reallocation failed\n");
- free(offsets);
- return -1;
- }
- offsets = new_offsets;
- }
-
- offsets[count++] = pos;
- printf("Found TAV header at offset: 0x%lX (%lu)\n", pos, pos);
-
- // Skip past this header (32 bytes total)
- uint64_t packet_pos = pos + 32;
- fseek(in, packet_pos, SEEK_SET);
-
- // Smart packet-wise skipping
- while (1) {
- uint8_t packet_type;
- if (fread(&packet_type, 1, 1, in) != 1) {
- // End of file
- break;
- }
-
- // Check if this is the start of next TAV file (0x1F is prohibited as packet type)
- if (packet_type == 0x1F) {
- // Rewind 1 byte to re-read as magic at the top of outer loop
- fseek(in, packet_pos, SEEK_SET);
- break;
- }
-
- // printf("TAV Packet 0x%02X at 0x%lX\n", packet_type, packet_pos);
-
- // Sync packets (0xFE, 0xFF) have no payload size - they're single-byte packets
- if (packet_type == 0xFE || packet_type == 0xFF) {
- packet_pos += 1;
- fseek(in, packet_pos, SEEK_SET);
- continue;
- }
-
- // Read payload size (uint32, little-endian)
- uint32_t payload_size = 0;
- if (fread(&payload_size, 4, 1, in) != 1) {
- // End of file
- break;
- }
-
- // Skip packet: 1 byte (type) + 4 bytes (size) + payload_size
- packet_pos += 1 + 4 + payload_size;
- fseek(in, packet_pos, SEEK_SET);
- }
- } else {
- // Move forward by 1 byte for next search
- fseek(in, pos + 1, SEEK_SET);
- }
- }
-
- *offsets_out = offsets;
- return count;
-}
-
-int main(int argc, char *argv[]) {
- if (argc < 3 || argc > 4) {
- fprintf(stderr, "Usage: %s [track_names.txt]\n", argv[0]);
- fprintf(stderr, "Creates a 4KB UCF payload for concatenated TAV file\n");
- fprintf(stderr, " track_names.txt: Optional file with track names (one per line)\n");
- return 1;
- }
-
- const char *input_path = argv[1];
- const char *output_path = argv[2];
- const char *names_path = (argc == 4) ? argv[3] : NULL;
-
- // Read track names if provided
- char **track_names = NULL;
- int num_names = 0;
- if (names_path) {
- track_names = read_track_names(names_path, &num_names);
- if (track_names) {
- printf("Loaded %d track name(s) from '%s'\n", num_names, names_path);
- } else {
- fprintf(stderr, "Warning: Could not read track names from '%s', using defaults\n", names_path);
- }
- }
-
- // Open input file
- FILE *in = fopen(input_path, "rb");
- if (!in) {
- fprintf(stderr, "Error: Cannot open input file '%s'\n", input_path);
- if (track_names) {
- for (int i = 0; i < num_names; i++) {
- free(track_names[i]);
- }
- free(track_names);
- }
- return 1;
- }
-
- // Find all TAV headers
- uint64_t *offsets = NULL;
- int num_tracks = find_tav_headers(in, &offsets);
- fclose(in);
-
- if (num_tracks < 0) {
- fprintf(stderr, "Error: Failed to scan input file\n");
- if (track_names) {
- for (int i = 0; i < num_names; i++) {
- free(track_names[i]);
- }
- free(track_names);
- }
- return 1;
- }
-
- if (num_tracks == 0) {
- fprintf(stderr, "Error: No TAV headers found in input file\n");
- free(offsets);
- if (track_names) {
- for (int i = 0; i < num_names; i++) {
- free(track_names[i]);
- }
- free(track_names);
- }
- return 1;
- }
-
- printf("\nFound %d TAV header(s)\n", num_tracks);
-
- // Create output UCF file
- FILE *out = fopen(output_path, "wb");
- if (!out) {
- fprintf(stderr, "Error: Cannot create output file '%s'\n", output_path);
- free(offsets);
- if (track_names) {
- for (int i = 0; i < num_names; i++) {
- free(track_names[i]);
- }
- free(track_names);
- }
- return 1;
- }
-
- // Write TAV header-only payload (File Role = 1)
- write_tav_header_only(out);
- printf("Written TAV header-only payload (%d bytes)\n", TAV_HEADER_SIZE);
-
- // Write UCF header
- write_ucf_header(out, num_tracks);
-
- // Write cue elements
- for (int i = 0; i < num_tracks; i++) {
- char default_name[32];
- const char *name;
-
- // Use custom name if available, otherwise generate default
- if (track_names && i < num_names) {
- name = track_names[i];
- } else {
- snprintf(default_name, sizeof(default_name), "Track %d", i + 1);
- name = default_name;
- }
-
- write_cue_element(out, offsets[i], name);
- printf("Written cue element: '%s' at offset 0x%lX (biased: 0x%lX)\n",
- name, offsets[i], offsets[i] + TAV_OFFSET_BIAS);
- }
-
- // Get current file position
- long current_pos = ftell(out);
-
- // Fill remaining space with zeros to reach TAV header + 4KB UCF
- size_t target_size = TAV_HEADER_SIZE + UCF_SIZE;
- if (current_pos < target_size) {
- size_t remaining = target_size - current_pos;
- uint8_t *zeros = calloc(remaining, 1);
- if (zeros) {
- fwrite(zeros, 1, remaining, out);
- free(zeros);
- }
- }
-
- fclose(out);
- free(offsets);
-
- // Clean up track names
- if (track_names) {
- for (int i = 0; i < num_names; i++) {
- free(track_names[i]);
- }
- free(track_names);
- }
-
- printf("\nTAV+UCF payload created successfully: %s\n", output_path);
- printf("File size: %zu bytes (TAV header: %d + UCF: %d)\n",
- (size_t)(TAV_HEADER_SIZE + UCF_SIZE), TAV_HEADER_SIZE, UCF_SIZE);
- printf("\nTo create seekable TAV file, prepend this payload to your concatenated TAV file:\n");
- printf(" cat %s input.tav > output_seekable.tav\n", output_path);
-
- return 0;
-}
diff --git a/video_encoder/encoder_ipf1d.c b/video_encoder/encoder_ipf1d.c
deleted file mode 100644
index d546e2a..0000000
--- a/video_encoder/encoder_ipf1d.c
+++ /dev/null
@@ -1,935 +0,0 @@
-#define _GNU_SOURCE
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-// TVDOS Movie format constants
-#define TVDOS_MAGIC "\x1F\x54\x53\x56\x4D\x4D\x4F\x56" // "\x1FTSVM MOV"
-#define IPF_BLOCK_SIZE 12
-
-// iPF1-delta opcodes
-#define SKIP_OP 0x00
-#define PATCH_OP 0x01
-#define REPEAT_OP 0x02
-#define END_OP 0xFF
-
-// Video packet types
-#define IPF1_PACKET_TYPE 0x04, 0x00 // iPF Type 1 (4 + 0)
-#define IPF1_DELTA_PACKET_TYPE 0x04, 0x02 // iPF Type 1 delta
-#define SYNC_PACKET_TYPE 0xFF, 0xFF // Sync packet
-
-// Audio constants
-#define MP2_SAMPLE_RATE 32000
-#define MP2_DEFAULT_PACKET_SIZE 0x240
-#define MP2_PACKET_TYPE_BASE 0x11
-
-// Default values
-#define DEFAULT_WIDTH 560
-#define DEFAULT_HEIGHT 448
-#define TEMP_AUDIO_FILE "/tmp/tvdos_temp_audio.mp2"
-
-typedef struct {
- char *input_file;
- char *output_file;
- int width;
- int height;
- int fps;
- int total_frames;
- double duration;
- int has_audio;
- int output_to_stdout;
-
- // Internal buffers
- uint8_t *previous_ipf_frame;
- uint8_t *current_ipf_frame;
- uint8_t *delta_buffer;
- uint8_t *rgb_buffer;
- uint8_t *compressed_buffer;
- uint8_t *mp2_buffer;
- size_t frame_buffer_size;
-
- // Audio handling
- FILE *mp2_file;
- int mp2_packet_size;
- int mp2_rate_index;
- size_t audio_remaining;
- int audio_frames_in_buffer;
- int target_audio_buffer_size;
-
- // FFmpeg processes
- FILE *ffmpeg_video_pipe;
- FILE *ffmpeg_audio_pipe;
-
- // Progress tracking
- struct timeval start_time;
- struct timeval last_progress_time;
- size_t total_output_bytes;
-
- // Dithering mode
- int dither_mode;
-} encoder_config_t;
-
-// CORRECTED YCoCg conversion matching Kotlin implementation
-typedef struct {
- float y, co, cg;
-} ycocg_t;
-
-static ycocg_t rgb_to_ycocg_correct(uint8_t r, uint8_t g, uint8_t b, float ditherThreshold) {
- ycocg_t result;
- float rf = floor((ditherThreshold / 15.0 + r / 255.0) * 15.0) / 15.0;
- float gf = floor((ditherThreshold / 15.0 + g / 255.0) * 15.0) / 15.0;
- float bf = floor((ditherThreshold / 15.0 + b / 255.0) * 15.0) / 15.0;
-
- // CORRECTED: Match Kotlin implementation exactly
- float co = rf - bf; // co = r - b [-1..1]
- float tmp = bf + co / 2.0f; // tmp = b + co/2
- float cg = gf - tmp; // cg = g - tmp [-1..1]
- float y = tmp + cg / 2.0f; // y = tmp + cg/2 [0..1]
-
- result.y = y;
- result.co = co;
- result.cg = cg;
-
- return result;
-}
-
-static int quantise_4bit_y(float value) {
- // Y quantisation: round(y * 15)
- return (int)round(fmaxf(0.0f, fminf(15.0f, value * 15.0f)));
-}
-
-static int chroma_to_four_bits(float f) {
- // CORRECTED: Match Kotlin chromaToFourBits function exactly
- // return (round(f * 8) + 7).coerceIn(0..15)
- int result = (int)round(f * 8.0f) + 7;
- return fmaxf(0, fminf(15, result));
-}
-
-// Parse resolution string like "1024x768"
-static int parse_resolution(const char *res_str, int *width, int *height) {
- if (!res_str) return 0;
- return sscanf(res_str, "%dx%d", width, height) == 2;
-}
-
-// Execute command and capture output
-static char *execute_command(const char *command) {
- FILE *pipe = popen(command, "r");
- if (!pipe) return NULL;
-
- char *result = malloc(4096);
- size_t len = fread(result, 1, 4095, pipe);
- result[len] = '\0';
-
- pclose(pipe);
- return result;
-}
-
-// Get video metadata using ffprobe
-static int get_video_metadata(encoder_config_t *config) {
- char command[1024];
- char *output;
-
- // Get frame count
- snprintf(command, sizeof(command),
- "ffprobe -v quiet -select_streams v:0 -count_frames -show_entries stream=nb_read_frames -of csv=p=0 \"%s\"",
- config->input_file);
- output = execute_command(command);
- if (!output) {
- fprintf(stderr, "Failed to get frame count\n");
- return 0;
- }
- config->total_frames = atoi(output);
- free(output);
-
- // Get frame rate
- snprintf(command, sizeof(command),
- "ffprobe -v quiet -select_streams v:0 -show_entries stream=r_frame_rate -of csv=p=0 \"%s\"",
- config->input_file);
- output = execute_command(command);
- if (!output) {
- fprintf(stderr, "Failed to get frame rate\n");
- return 0;
- }
-
- // Parse framerate (could be "30/1" or "29.97")
- int num, den;
- if (sscanf(output, "%d/%d", &num, &den) == 2) {
- config->fps = (den > 0) ? (num / den) : 30;
- } else {
- config->fps = (int)round(atof(output));
- }
- free(output);
-
- // Get duration
- snprintf(command, sizeof(command),
- "ffprobe -v quiet -show_entries format=duration -of csv=p=0 \"%s\"",
- config->input_file);
- output = execute_command(command);
- if (output) {
- config->duration = atof(output);
- free(output);
- }
-
- // Check if has audio
- snprintf(command, sizeof(command),
- "ffprobe -v quiet -select_streams a:0 -show_entries stream=index -of csv=p=0 \"%s\"",
- config->input_file);
- output = execute_command(command);
- config->has_audio = (output && strlen(output) > 0 && atoi(output) >= 0);
- if (output) free(output);
-
- // Validate frame count using duration if needed
- if (config->total_frames <= 0 && config->duration > 0) {
- config->total_frames = (int)(config->duration * config->fps);
- }
-
- fprintf(stderr, "Video metadata:\n");
- fprintf(stderr, " Frames: %d\n", config->total_frames);
- fprintf(stderr, " FPS: %d\n", config->fps);
- fprintf(stderr, " Duration: %.2fs\n", config->duration);
- fprintf(stderr, " Audio: %s\n", config->has_audio ? "Yes" : "No");
- fprintf(stderr, " Resolution: %dx%d\n", config->width, config->height);
-
- return (config->total_frames > 0 && config->fps > 0);
-}
-
-// Start FFmpeg process for video conversion
-static int start_video_conversion(encoder_config_t *config) {
- char command[2048];
- snprintf(command, sizeof(command),
- "ffmpeg -i \"%s\" -f rawvideo -pix_fmt rgb24 -vf scale=%d:%d:force_original_aspect_ratio=increase,crop=%d:%d -y - 2>/dev/null",
- config->input_file, config->width, config->height, config->width, config->height);
-
- config->ffmpeg_video_pipe = popen(command, "r");
- return (config->ffmpeg_video_pipe != NULL);
-}
-
-// Start FFmpeg process for audio conversion
-static int start_audio_conversion(encoder_config_t *config) {
- if (!config->has_audio) return 1;
-
- char command[2048];
- snprintf(command, sizeof(command),
- "ffmpeg -i \"%s\" -acodec libtwolame -psymodel 4 -b:a 192k -ar %d -ac 2 -y \"%s\" 2>/dev/null",
- config->input_file, MP2_SAMPLE_RATE, TEMP_AUDIO_FILE);
-
- int result = system(command);
- if (result == 0) {
- config->mp2_file = fopen(TEMP_AUDIO_FILE, "rb");
- if (config->mp2_file) {
- fseek(config->mp2_file, 0, SEEK_END);
- config->audio_remaining = ftell(config->mp2_file);
- fseek(config->mp2_file, 0, SEEK_SET);
- return 1;
- }
- }
-
- fprintf(stderr, "Warning: Failed to convert audio, proceeding without audio\n");
- config->has_audio = 0;
- return 1;
-}
-
-// Write variable-length integer
-static void write_varint(uint8_t **ptr, uint32_t value) {
- while (value >= 0x80) {
- **ptr = (uint8_t)((value & 0x7F) | 0x80);
- (*ptr)++;
- value >>= 7;
- }
- **ptr = (uint8_t)(value & 0x7F);
- (*ptr)++;
-}
-
-// Get MP2 packet size and rate index
-static int get_mp2_packet_size(uint8_t *header) {
- int bitrate_index = (header[2] >> 4) & 0xF;
- int padding_bit = (header[2] >> 1) & 0x1;
-
- int bitrates[] = {0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, -1};
- int bitrate = bitrates[bitrate_index];
-
- if (bitrate <= 0) return MP2_DEFAULT_PACKET_SIZE;
-
- int frame_size = (144 * bitrate * 1000) / MP2_SAMPLE_RATE + padding_bit;
- return frame_size;
-}
-
-static int mp2_packet_size_to_rate_index(int packet_size, int is_mono) {
- int rate_index;
- switch (packet_size) {
- case 144: rate_index = 0; break;
- case 216: rate_index = 2; break;
- case 252: rate_index = 4; break;
- case 288: rate_index = 6; break;
- case 360: rate_index = 8; break;
- case 432: rate_index = 10; break;
- case 504: rate_index = 12; break;
- case 576: rate_index = 14; break;
- case 720: rate_index = 16; break;
- case 864: rate_index = 18; break;
- case 1008: rate_index = 20; break;
- case 1152: rate_index = 22; break;
- case 1440: rate_index = 24; break;
- case 1728: rate_index = 26; break;
- default: rate_index = 14; break;
- }
- return rate_index + (is_mono ? 1 : 0);
-}
-
-// Gzip compress function (instead of zlib)
-static size_t gzip_compress(uint8_t *src, size_t src_len, uint8_t *dst, size_t dst_max) {
- z_stream stream = {0};
- stream.next_in = src;
- stream.avail_in = src_len;
- stream.next_out = dst;
- stream.avail_out = dst_max;
-
- // Use deflateInit2 with gzip format
- if (deflateInit2(&stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 15 + 16, 8, Z_DEFAULT_STRATEGY) != Z_OK) {
- return 0;
- }
-
- if (deflate(&stream, Z_FINISH) != Z_STREAM_END) {
- deflateEnd(&stream);
- return 0;
- }
-
- size_t compressed_size = stream.total_out;
- deflateEnd(&stream);
- return compressed_size;
-}
-
-// Bayer dithering kernels (4 patterns, each 4x4)
-static const float bayerKernels[4][16] = {
- { // Pattern 0
- (0.0f + 0.5f) / 16.0f, (8.0f + 0.5f) / 16.0f, (2.0f + 0.5f) / 16.0f, (10.0f + 0.5f) / 16.0f,
- (12.0f + 0.5f) / 16.0f, (4.0f + 0.5f) / 16.0f, (14.0f + 0.5f) / 16.0f, (6.0f + 0.5f) / 16.0f,
- (3.0f + 0.5f) / 16.0f, (11.0f + 0.5f) / 16.0f, (1.0f + 0.5f) / 16.0f, (9.0f + 0.5f) / 16.0f,
- (15.0f + 0.5f) / 16.0f, (7.0f + 0.5f) / 16.0f, (13.0f + 0.5f) / 16.0f, (5.0f + 0.5f) / 16.0f
- },
- { // Pattern 1
- (8.0f + 0.5f) / 16.0f, (2.0f + 0.5f) / 16.0f, (10.0f + 0.5f) / 16.0f, (0.0f + 0.5f) / 16.0f,
- (4.0f + 0.5f) / 16.0f, (14.0f + 0.5f) / 16.0f, (6.0f + 0.5f) / 16.0f, (12.0f + 0.5f) / 16.0f,
- (11.0f + 0.5f) / 16.0f, (1.0f + 0.5f) / 16.0f, (9.0f + 0.5f) / 16.0f, (3.0f + 0.5f) / 16.0f,
- (7.0f + 0.5f) / 16.0f, (13.0f + 0.5f) / 16.0f, (5.0f + 0.5f) / 16.0f, (15.0f + 0.5f) / 16.0f
- },
- { // Pattern 2
- (7.0f + 0.5f) / 16.0f, (13.0f + 0.5f) / 16.0f, (5.0f + 0.5f) / 16.0f, (15.0f + 0.5f) / 16.0f,
- (8.0f + 0.5f) / 16.0f, (2.0f + 0.5f) / 16.0f, (10.0f + 0.5f) / 16.0f, (0.0f + 0.5f) / 16.0f,
- (4.0f + 0.5f) / 16.0f, (14.0f + 0.5f) / 16.0f, (6.0f + 0.5f) / 16.0f, (12.0f + 0.5f) / 16.0f,
- (11.0f + 0.5f) / 16.0f, (1.0f + 0.5f) / 16.0f, (9.0f + 0.5f) / 16.0f, (3.0f + 0.5f) / 16.0f
- },
- { // Pattern 3
- (15.0f + 0.5f) / 16.0f, (7.0f + 0.5f) / 16.0f, (13.0f + 0.5f) / 16.0f, (5.0f + 0.5f) / 16.0f,
- (0.0f + 0.5f) / 16.0f, (8.0f + 0.5f) / 16.0f, (2.0f + 0.5f) / 16.0f, (10.0f + 0.5f) / 16.0f,
- (12.0f + 0.5f) / 16.0f, (4.0f + 0.5f) / 16.0f, (14.0f + 0.5f) / 16.0f, (6.0f + 0.5f) / 16.0f,
- (3.0f + 0.5f) / 16.0f, (11.0f + 0.5f) / 16.0f, (1.0f + 0.5f) / 16.0f, (9.0f + 0.5f) / 16.0f
- }
-};
-
-// CORRECTED: Encode a 4x4 block to iPF1 format matching Kotlin implementation
-static void encode_ipf1_block_correct(uint8_t *rgb_data, int width, int height, int block_x, int block_y,
- int channels, int pattern, uint8_t *output) {
- ycocg_t pixels[16];
- int y_values[16];
- float co_values[16]; // Keep full precision for subsampling
- float cg_values[16]; // Keep full precision for subsampling
-
- // Convert 4x4 block to YCoCg using corrected transform
- for (int py = 0; py < 4; py++) {
- for (int px = 0; px < 4; px++) {
- int src_x = block_x * 4 + px;
- int src_y = block_y * 4 + py;
- float t = (pattern < 0) ? 0.0f : bayerKernels[pattern % 4][4 * (py % 4) + (px % 4)];
- int idx = py * 4 + px;
-
- if (src_x < width && src_y < height) {
- int pixel_offset = (src_y * width + src_x) * channels;
- uint8_t r = rgb_data[pixel_offset];
- uint8_t g = rgb_data[pixel_offset + 1];
- uint8_t b = rgb_data[pixel_offset + 2];
- pixels[idx] = rgb_to_ycocg_correct(r, g, b, t);
- } else {
- pixels[idx] = (ycocg_t){0.0f, 0.0f, 0.0f};
- }
-
- y_values[idx] = quantise_4bit_y(pixels[idx].y);
- co_values[idx] = pixels[idx].co;
- cg_values[idx] = pixels[idx].cg;
- }
- }
-
- // CORRECTED: Chroma subsampling (4:2:0 for iPF1) with correct averaging
- int cos1 = chroma_to_four_bits((co_values[0] + co_values[1] + co_values[4] + co_values[5]) / 4.0f);
- int cos2 = chroma_to_four_bits((co_values[2] + co_values[3] + co_values[6] + co_values[7]) / 4.0f);
- int cos3 = chroma_to_four_bits((co_values[8] + co_values[9] + co_values[12] + co_values[13]) / 4.0f);
- int cos4 = chroma_to_four_bits((co_values[10] + co_values[11] + co_values[14] + co_values[15]) / 4.0f);
-
- int cgs1 = chroma_to_four_bits((cg_values[0] + cg_values[1] + cg_values[4] + cg_values[5]) / 4.0f);
- int cgs2 = chroma_to_four_bits((cg_values[2] + cg_values[3] + cg_values[6] + cg_values[7]) / 4.0f);
- int cgs3 = chroma_to_four_bits((cg_values[8] + cg_values[9] + cg_values[12] + cg_values[13]) / 4.0f);
- int cgs4 = chroma_to_four_bits((cg_values[10] + cg_values[11] + cg_values[14] + cg_values[15]) / 4.0f);
-
- // CORRECTED: Pack into iPF1 format matching Kotlin exactly
- // Co values (2 bytes): cos2|cos1, cos4|cos3
- output[0] = ((cos2 << 4) | cos1);
- output[1] = ((cos4 << 4) | cos3);
-
- // Cg values (2 bytes): cgs2|cgs1, cgs4|cgs3
- output[2] = ((cgs2 << 4) | cgs1);
- output[3] = ((cgs4 << 4) | cgs3);
-
- // CORRECTED: Y values (8 bytes) with correct ordering from Kotlin
- output[4] = ((y_values[1] << 4) | y_values[0]); // Y1|Y0
- output[5] = ((y_values[5] << 4) | y_values[4]); // Y5|Y4
- output[6] = ((y_values[3] << 4) | y_values[2]); // Y3|Y2
- output[7] = ((y_values[7] << 4) | y_values[6]); // Y7|Y6
- output[8] = ((y_values[9] << 4) | y_values[8]); // Y9|Y8
- output[9] = ((y_values[13] << 4) | y_values[12]); // Y13|Y12
- output[10] = ((y_values[11] << 4) | y_values[10]); // Y11|Y10
- output[11] = ((y_values[15] << 4) | y_values[14]); // Y15|Y14
-}
-
-// Helper function for contrast weighting
-static double contrast_weight(int v1, int v2, int delta, int weight) {
- double avg = (v1 + v2) / 2.0;
- double contrast = (avg < 4 || avg > 11) ? 1.5 : 1.0;
- return delta * weight * contrast;
-}
-
-// Check if two iPF1 blocks are significantly different
-static int is_significantly_different(uint8_t *block_a, uint8_t *block_b) {
- double score = 0.0;
-
- // Co values (bytes 0-1)
- uint16_t co_a = block_a[0] | (block_a[1] << 8);
- uint16_t co_b = block_b[0] | (block_b[1] << 8);
- for (int i = 0; i < 4; i++) {
- int va = (co_a >> (i * 4)) & 0xF;
- int vb = (co_b >> (i * 4)) & 0xF;
- int delta = abs(va - vb);
- score += contrast_weight(va, vb, delta, 3);
- }
-
- // Cg values (bytes 2-3)
- uint16_t cg_a = block_a[2] | (block_a[3] << 8);
- uint16_t cg_b = block_b[2] | (block_b[3] << 8);
- for (int i = 0; i < 4; i++) {
- int va = (cg_a >> (i * 4)) & 0xF;
- int vb = (cg_b >> (i * 4)) & 0xF;
- int delta = abs(va - vb);
- score += contrast_weight(va, vb, delta, 3);
- }
-
- // Y values (bytes 4-11)
- for (int i = 4; i < 12; i++) {
- int byte_a = block_a[i] & 0xFF;
- int byte_b = block_b[i] & 0xFF;
-
- int y_a_high = (byte_a >> 4) & 0xF;
- int y_a_low = byte_a & 0xF;
- int y_b_high = (byte_b >> 4) & 0xF;
- int y_b_low = byte_b & 0xF;
-
- int delta_high = abs(y_a_high - y_b_high);
- int delta_low = abs(y_a_low - y_b_low);
-
- score += contrast_weight(y_a_high, y_b_high, delta_high, 2);
- score += contrast_weight(y_a_low, y_b_low, delta_low, 2);
- }
-
- return score > 4.0;
-}
-
-// Encode iPF1 frame to buffer
-static void encode_ipf1_frame(uint8_t *rgb_data, int width, int height, int channels, int pattern,
- uint8_t *ipf_buffer) {
- int blocks_per_row = (width + 3) / 4;
- int blocks_per_col = (height + 3) / 4;
-
- for (int block_y = 0; block_y < blocks_per_col; block_y++) {
- for (int block_x = 0; block_x < blocks_per_row; block_x++) {
- int block_index = block_y * blocks_per_row + block_x;
- uint8_t *output_block = ipf_buffer + block_index * IPF_BLOCK_SIZE;
- encode_ipf1_block_correct(rgb_data, width, height, block_x, block_y, channels, pattern, output_block);
- }
- }
-}
-
-// Create iPF1-delta encoded frame
-static size_t encode_ipf1_delta(uint8_t *previous_frame, uint8_t *current_frame,
- int width, int height, uint8_t *delta_buffer) {
- int blocks_per_row = (width + 3) / 4;
- int blocks_per_col = (height + 3) / 4;
- int total_blocks = blocks_per_row * blocks_per_col;
-
- uint8_t *output_ptr = delta_buffer;
- int skip_count = 0;
- uint8_t *patch_blocks = malloc(total_blocks * IPF_BLOCK_SIZE);
- int patch_count = 0;
-
- for (int block_index = 0; block_index < total_blocks; block_index++) {
- uint8_t *prev_block = previous_frame + block_index * IPF_BLOCK_SIZE;
- uint8_t *curr_block = current_frame + block_index * IPF_BLOCK_SIZE;
-
- if (is_significantly_different(prev_block, curr_block)) {
- if (skip_count > 0) {
- *output_ptr++ = SKIP_OP;
- write_varint(&output_ptr, skip_count);
- skip_count = 0;
- }
-
- memcpy(patch_blocks + patch_count * IPF_BLOCK_SIZE, curr_block, IPF_BLOCK_SIZE);
- patch_count++;
- } else {
- if (patch_count > 0) {
- *output_ptr++ = PATCH_OP;
- write_varint(&output_ptr, patch_count);
- memcpy(output_ptr, patch_blocks, patch_count * IPF_BLOCK_SIZE);
- output_ptr += patch_count * IPF_BLOCK_SIZE;
- patch_count = 0;
- }
- skip_count++;
- }
- }
-
- if (patch_count > 0) {
- *output_ptr++ = PATCH_OP;
- write_varint(&output_ptr, patch_count);
- memcpy(output_ptr, patch_blocks, patch_count * IPF_BLOCK_SIZE);
- output_ptr += patch_count * IPF_BLOCK_SIZE;
- }
-
- *output_ptr++ = END_OP;
-
- free(patch_blocks);
- return output_ptr - delta_buffer;
-}
-
-// Get current time in seconds
-static double get_current_time_sec(struct timeval *tv) {
- gettimeofday(tv, NULL);
- return tv->tv_sec + tv->tv_usec / 1000000.0;
-}
-
-// Display progress information similar to FFmpeg
-static void display_progress(encoder_config_t *config, int frame_num) {
- struct timeval current_time;
- double current_sec = get_current_time_sec(¤t_time);
-
- // Only update progress once per second
- double last_progress_sec = config->last_progress_time.tv_sec + config->last_progress_time.tv_usec / 1000000.0;
- if (current_sec - last_progress_sec < 1.0) {
- return;
- }
-
- config->last_progress_time = current_time;
-
- // Calculate timing
- double start_sec = config->start_time.tv_sec + config->start_time.tv_usec / 1000000.0;
- double elapsed_sec = current_sec - start_sec;
- double current_video_time = (double)frame_num / config->fps;
- double fps = frame_num / elapsed_sec;
- double speed = (elapsed_sec > 0) ? current_video_time / elapsed_sec : 0.0;
- double bitrate = (elapsed_sec > 0) ? (config->total_output_bytes * 8.0 / 1024.0) / elapsed_sec : 0.0;
-
- // Format output size in human readable format
- char size_str[32];
- if (config->total_output_bytes >= 1024 * 1024) {
- snprintf(size_str, sizeof(size_str), "%.1fMB", config->total_output_bytes / (1024.0 * 1024.0));
- } else if (config->total_output_bytes >= 1024) {
- snprintf(size_str, sizeof(size_str), "%.1fkB", config->total_output_bytes / 1024.0);
- } else {
- snprintf(size_str, sizeof(size_str), "%zuB", config->total_output_bytes);
- }
-
- // Format current time as HH:MM:SS.xx
- int hours = (int)(current_video_time / 3600);
- int minutes = (int)((current_video_time - hours * 3600) / 60);
- double seconds = current_video_time - hours * 3600 - minutes * 60;
-
- // Print progress line (overwrite previous line)
- fprintf(stderr, "\rframe=%d fps=%.1f size=%s time=%02d:%02d:%05.2f bitrate=%.1fkbits/s speed=%4.2fx",
- frame_num, fps, size_str, hours, minutes, seconds, bitrate, speed);
- fflush(stderr);
-}
-
-// Process audio for current frame
-static int process_audio(encoder_config_t *config, int frame_num, FILE *output) {
- if (!config->has_audio || !config->mp2_file || config->audio_remaining <= 0) {
- return 1;
- }
-
- // Initialise packet size on first frame
- if (config->mp2_packet_size == 0) {
- uint8_t header[4];
- if (fread(header, 1, 4, config->mp2_file) != 4) return 1;
- fseek(config->mp2_file, 0, SEEK_SET);
-
- config->mp2_packet_size = get_mp2_packet_size(header);
- int is_mono = (header[3] >> 6) == 3;
- config->mp2_rate_index = mp2_packet_size_to_rate_index(config->mp2_packet_size, is_mono);
- }
-
- // Calculate how much audio time each frame represents (in seconds)
- double frame_audio_time = 1.0 / config->fps;
-
- // Calculate how much audio time each MP2 packet represents
- // MP2 frame contains 1152 samples at 32kHz = 0.036 seconds
- double packet_audio_time = 1152.0 / MP2_SAMPLE_RATE;
-
- // Estimate how many packets we consume per video frame
- double packets_per_frame = frame_audio_time / packet_audio_time;
-
- // Only insert audio when buffer would go below 2 frames
- // Initialise with 2 packets on first frame to prime the buffer
- int packets_to_insert = 0;
- if (frame_num == 1) {
- packets_to_insert = 2;
- config->audio_frames_in_buffer = 2;
- } else {
- // Simulate buffer consumption (packets consumed per frame)
- config->audio_frames_in_buffer -= (int)ceil(packets_per_frame);
-
- // Only insert packets when buffer gets low (≤ 2 frames)
- if (config->audio_frames_in_buffer <= 2) {
- packets_to_insert = config->target_audio_buffer_size - config->audio_frames_in_buffer;
- packets_to_insert = (packets_to_insert > 0) ? packets_to_insert : 1;
- }
- }
-
- // Insert the calculated number of audio packets
- for (int q = 0; q < packets_to_insert; q++) {
- size_t bytes_to_read = config->mp2_packet_size;
- if (bytes_to_read > config->audio_remaining) {
- bytes_to_read = config->audio_remaining;
- }
-
- size_t bytes_read = fread(config->mp2_buffer, 1, bytes_to_read, config->mp2_file);
- if (bytes_read == 0) break;
-
- uint8_t audio_packet_type[2] = {config->mp2_rate_index, MP2_PACKET_TYPE_BASE};
- fwrite(audio_packet_type, 1, 2, output);
- fwrite(config->mp2_buffer, 1, bytes_read, output);
-
- // Track audio bytes written
- config->total_output_bytes += 2 + bytes_read;
- config->audio_remaining -= bytes_read;
- config->audio_frames_in_buffer++;
- }
-
- return 1;
-}
-
-// Write TVDOS header
-static void write_tvdos_header(encoder_config_t *config, FILE *output) {
- fwrite(TVDOS_MAGIC, 1, 8, output);
- fwrite(&config->width, 2, 1, output);
- fwrite(&config->height, 2, 1, output);
- fwrite(&config->fps, 2, 1, output);
- fwrite(&config->total_frames, 4, 1, output);
-
- uint16_t unused = 0x00FF;
- fwrite(&unused, 2, 1, output);
-
- int audio_sample_size = 2 * (((MP2_SAMPLE_RATE / config->fps) + 1));
- int audio_queue_size = config->has_audio ?
- (int)ceil(audio_sample_size / 2304.0) + 1 : 0;
-
- uint16_t audio_queue_info = config->has_audio ?
- (MP2_DEFAULT_PACKET_SIZE >> 2) | (audio_queue_size << 12) : 0x0000;
- fwrite(&audio_queue_info, 2, 1, output);
-
- // Store target buffer size for audio timing
- config->target_audio_buffer_size = audio_queue_size;
-
- uint8_t reserved[10] = {0};
- fwrite(reserved, 1, 10, output);
-}
-
-// Initialise encoder configuration
-static encoder_config_t *init_encoder_config() {
- encoder_config_t *config = calloc(1, sizeof(encoder_config_t));
- if (!config) return NULL;
-
- config->width = DEFAULT_WIDTH;
- config->height = DEFAULT_HEIGHT;
-
- return config;
-}
-
-// Allocate encoder buffers
-static int allocate_buffers(encoder_config_t *config) {
- config->frame_buffer_size = ((config->width + 3) / 4) * ((config->height + 3) / 4) * IPF_BLOCK_SIZE;
-
- config->rgb_buffer = malloc(config->width * config->height * 3);
- config->previous_ipf_frame = malloc(config->frame_buffer_size);
- config->current_ipf_frame = malloc(config->frame_buffer_size);
- config->delta_buffer = malloc(config->frame_buffer_size * 2);
- config->compressed_buffer = malloc(config->frame_buffer_size * 2);
- config->mp2_buffer = malloc(2048);
-
- return (config->rgb_buffer && config->previous_ipf_frame &&
- config->current_ipf_frame && config->delta_buffer &&
- config->compressed_buffer && config->mp2_buffer);
-}
-
-// Process one frame - CORRECTED ORDER: Audio -> Video -> Sync
-static int process_frame(encoder_config_t *config, int frame_num, int is_keyframe, FILE *output) {
- // Read RGB data from FFmpeg pipe first
- size_t rgb_size = config->width * config->height * 3;
- if (fread(config->rgb_buffer, 1, rgb_size, config->ffmpeg_video_pipe) != rgb_size) {
- if (feof(config->ffmpeg_video_pipe)) return 0;
- return -1;
- }
-
- // Step 1: Process audio FIRST (matches working file pattern)
- if (!process_audio(config, frame_num, output)) {
- return -1;
- }
-
- // Step 2: Encode and write video
- int pattern;
- switch (config->dither_mode) {
- case 0: pattern = -1; break; // No dithering
- case 1: pattern = 0; break; // Static pattern
- case 2: pattern = frame_num % 4; break; // Dynamic pattern
- default: pattern = 0; break; // Fallback to static
- }
- encode_ipf1_frame(config->rgb_buffer, config->width, config->height, 3, pattern,
- config->current_ipf_frame);
-
- // Determine if we should use delta encoding
- int use_delta = 0;
- size_t data_size = config->frame_buffer_size;
- uint8_t *frame_data = config->current_ipf_frame;
-
- if (frame_num > 1 && !is_keyframe) {
- size_t delta_size = encode_ipf1_delta(config->previous_ipf_frame,
- config->current_ipf_frame,
- config->width, config->height,
- config->delta_buffer);
-
- if (delta_size < config->frame_buffer_size * 0.576) {
- use_delta = 1;
- data_size = delta_size;
- frame_data = config->delta_buffer;
- }
- }
-
- // Compress the frame data using gzip
- size_t compressed_size = gzip_compress(frame_data, data_size,
- config->compressed_buffer,
- config->frame_buffer_size * 2);
- if (compressed_size == 0) {
- fprintf(stderr, "Gzip compression failed\n");
- return -1;
- }
-
- // Write video packet
- if (use_delta) {
- uint8_t packet_type[2] = {IPF1_DELTA_PACKET_TYPE};
- fwrite(packet_type, 1, 2, output);
- } else {
- uint8_t packet_type[2] = {IPF1_PACKET_TYPE};
- fwrite(packet_type, 1, 2, output);
- }
-
- uint32_t size_le = compressed_size;
- fwrite(&size_le, 4, 1, output);
- fwrite(config->compressed_buffer, 1, compressed_size, output);
-
- // Step 3: Write sync packet AFTER video (matches working file pattern)
- uint8_t sync[2] = {SYNC_PACKET_TYPE};
- fwrite(sync, 1, 2, output);
-
- // Track video bytes written (packet type + size + compressed data + sync)
- config->total_output_bytes += 2 + 4 + compressed_size + 2;
-
- // Swap frame buffers
- uint8_t *temp = config->previous_ipf_frame;
- config->previous_ipf_frame = config->current_ipf_frame;
- config->current_ipf_frame = temp;
-
- // Display progress
- display_progress(config, frame_num);
-
- return 1;
-}
-
-// Cleanup function
-static void cleanup_config(encoder_config_t *config) {
- if (!config) return;
-
- if (config->ffmpeg_video_pipe) pclose(config->ffmpeg_video_pipe);
- if (config->mp2_file) fclose(config->mp2_file);
-
- free(config->input_file);
- free(config->output_file);
- free(config->rgb_buffer);
- free(config->previous_ipf_frame);
- free(config->current_ipf_frame);
- free(config->delta_buffer);
- free(config->compressed_buffer);
- free(config->mp2_buffer);
-
- // Remove temporary audio file
- unlink(TEMP_AUDIO_FILE);
-
- free(config);
-}
-
-// Print usage information
-static void print_usage(const char *program_name) {
- printf("TVDOS Movie Encoder\n\n");
- printf("Usage: %s [options] input_video\n\n", program_name);
- printf("Options:\n");
- printf(" -o, --output FILE Output TVDOS movie file (default: stdout)\n");
- printf(" -s, --size WxH Video resolution (default: 560x448)\n");
- printf(" -d, --dither MODE Dithering mode (default: 1)\n");
- printf(" 0: No dithering\n");
- printf(" 1: Static pattern\n");
- printf(" 2: Dynamic pattern (better quality, larger files)\n");
- printf(" -h, --help Show this help message\n\n");
- printf("Examples:\n");
- printf(" %s input.mp4 -o output.mov\n", program_name);
- printf(" %s input.avi -s 1024x768 -o output.mov\n", program_name);
- printf(" yt-dlp -o - \"https://youtube.com/watch?v=VIDEO_ID\" | ffmpeg -i pipe:0 -c copy temp.mp4 && %s temp.mp4 -o youtube_video.mov && rm temp.mp4\n", program_name);
-}
-
-int main(int argc, char *argv[]) {
- encoder_config_t *config = init_encoder_config();
- if (!config) {
- fprintf(stderr, "Failed to initialise encoder\n");
- return 1;
- }
-
- config->output_to_stdout = 1; // Default to stdout
- config->dither_mode = 1; // Default to static dithering
-
- // Parse command line arguments
- static struct option long_options[] = {
- {"output", required_argument, 0, 'o'},
- {"size", required_argument, 0, 's'},
- {"dither", required_argument, 0, 'd'},
- {"help", no_argument, 0, 'h'},
- {0, 0, 0, 0}
- };
-
- int c;
- while ((c = getopt_long(argc, argv, "o:s:d:h", long_options, NULL)) != -1) {
- switch (c) {
- case 'o':
- config->output_file = strdup(optarg);
- config->output_to_stdout = 0;
- break;
- case 's':
- if (!parse_resolution(optarg, &config->width, &config->height)) {
- fprintf(stderr, "Invalid resolution format: %s\n", optarg);
- cleanup_config(config);
- return 1;
- }
- break;
- case 'd':
- config->dither_mode = atoi(optarg);
- if (config->dither_mode < 0 || config->dither_mode > 2) {
- fprintf(stderr, "Invalid dither mode: %s (must be 0, 1, or 2)\n", optarg);
- cleanup_config(config);
- return 1;
- }
- break;
- case 'h':
- print_usage(argv[0]);
- cleanup_config(config);
- return 0;
- default:
- print_usage(argv[0]);
- cleanup_config(config);
- return 1;
- }
- }
-
- if (optind >= argc) {
- fprintf(stderr, "Error: Input video file required\n\n");
- print_usage(argv[0]);
- cleanup_config(config);
- return 1;
- }
-
- config->input_file = strdup(argv[optind]);
-
- // Get video metadata
- if (!get_video_metadata(config)) {
- fprintf(stderr, "Failed to analyze video metadata\n");
- cleanup_config(config);
- return 1;
- }
-
- // Allocate buffers
- if (!allocate_buffers(config)) {
- fprintf(stderr, "Failed to allocate memory buffers\n");
- cleanup_config(config);
- return 1;
- }
-
- // Start video conversion
- if (!start_video_conversion(config)) {
- fprintf(stderr, "Failed to start video conversion\n");
- cleanup_config(config);
- return 1;
- }
-
- // Start audio conversion
- if (!start_audio_conversion(config)) {
- fprintf(stderr, "Failed to start audio conversion\n");
- cleanup_config(config);
- return 1;
- }
-
- // Open output
- FILE *output = config->output_to_stdout ? stdout : fopen(config->output_file, "wb");
- if (!output) {
- fprintf(stderr, "Failed to open output file\n");
- cleanup_config(config);
- return 1;
- }
-
- // Write TVDOS header
- write_tvdos_header(config, output);
-
- // Initialise progress tracking
- gettimeofday(&config->start_time, NULL);
- config->last_progress_time = config->start_time;
- config->total_output_bytes = 8 + 2 + 2 + 2 + 4 + 2 + 2 + 10; // TVDOS header size
-
- // Process frames with correct order: Audio -> Video -> Sync
- for (int frame = 1; frame <= config->total_frames; frame++) {
- int is_keyframe = (frame == 1) || (frame % 30 == 0);
-
- int result = process_frame(config, frame, is_keyframe, output);
- if (result <= 0) {
- if (result == 0) {
- fprintf(stderr, "End of video at frame %d\n", frame);
- }
- break;
- }
- }
-
- // Final progress update and newline
- fprintf(stderr, "\n");
-
- if (!config->output_to_stdout) {
- fclose(output);
- fprintf(stderr, "Encoding complete: %s\n", config->output_file);
- }
-
- cleanup_config(config);
- return 0;
-}
diff --git a/video_encoder/encoder_tav_opencv.cpp b/video_encoder/encoder_tav_opencv.cpp
deleted file mode 100644
index f74d2d1..0000000
--- a/video_encoder/encoder_tav_opencv.cpp
+++ /dev/null
@@ -1,183 +0,0 @@
-// Created by CuriousTorvald and Claude on 2025-10-17
-// MPEG-style bidirectional block motion compensation for TAV encoder
-// Simplified: Single-level diamond search, variable blocks, overlaps, sub-pixel refinement
-
-#include
-#include
-#include
-#include
-
-extern "C" {
-
-// Dense optical flow estimation using Farneback algorithm
-// Computes flow at every pixel, then samples at block centers for motion vectors
-// Much more spatially coherent than independent block matching
-void estimate_optical_flow_motion(
- const float *current_y, // Current frame Y channel (width×height)
- const float *reference_y, // Reference frame Y channel
- int width, int height,
- int block_size, // Block size (e.g., 16)
- int16_t *mvs_x, // Output: motion vectors X (in 1/4-pixel units)
- int16_t *mvs_y // Output: motion vectors Y (in 1/4-pixel units)
-) {
- // Convert float Y channels to 8-bit grayscale for OpenCV
- cv::Mat cur_gray(height, width, CV_8UC1);
- cv::Mat ref_gray(height, width, CV_8UC1);
-
- // Detect if Y is in [0,1] range and scale to [0,255] if needed
- float y_min = current_y[0], y_max = current_y[0];
- for (int i = 1; i < width * height; i++) {
- if (current_y[i] < y_min) y_min = current_y[i];
- if (current_y[i] > y_max) y_max = current_y[i];
- }
- float scale = (y_max <= 1.1f) ? 255.0f : 1.0f;
-
- for (int y = 0; y < height; y++) {
- for (int x = 0; x < width; x++) {
- int idx = y * width + x;
- cur_gray.at(y, x) = (uint8_t)std::round(std::max(0.0f, std::min(255.0f, current_y[idx] * scale)));
- ref_gray.at(y, x) = (uint8_t)std::round(std::max(0.0f, std::min(255.0f, reference_y[idx] * scale)));
- }
- }
-
- // Compute dense optical flow using Farneback algorithm
- // IMPORTANT: We need BACKWARD flow (current → reference) for motion compensation
- // This tells us where to PULL pixels FROM in the reference frame
- cv::Mat flow;
- cv::calcOpticalFlowFarneback(
- cur_gray, // Current frame (source)
- ref_gray, // Reference frame (destination)
- flow, // Output flow (2-channel float: dx, dy per pixel)
- 0.5, // pyr_scale: pyramid scale (0.5 = each layer is half size)
- 3, // levels: number of pyramid levels
- 20, // winsize: averaging window size
- 3, // iterations: number of iterations at each pyramid level
- 5, // poly_n: size of pixel neighborhood (5 or 7)
- 1.2, // poly_sigma: standard deviation of Gaussian for polynomial expansion
- 0 // flags: 0 = normal, OPTFLOW_USE_INITIAL_FLOW = use input flow as initial estimate
- );
-
- // Sample flow at block centers to get motion vectors
- int num_blocks_x = (width + block_size - 1) / block_size;
- int num_blocks_y = (height + block_size - 1) / block_size;
-
- for (int by = 0; by < num_blocks_y; by++) {
- for (int bx = 0; bx < num_blocks_x; bx++) {
- int block_idx = by * num_blocks_x + bx;
-
- // Block center position
- int center_x = bx * block_size + block_size / 2;
- int center_y = by * block_size + block_size / 2;
-
- // Clamp to frame boundaries
- if (center_x >= width) center_x = width - 1;
- if (center_y >= height) center_y = height - 1;
-
- // Get flow at block center
- cv::Point2f flow_vec = flow.at(center_y, center_x);
-
- // Convert to 1/4-pixel units and store
- // Flow is in pixels, positive = motion to the right/down
- mvs_x[block_idx] = (int16_t)std::round(flow_vec.x * 4.0f);
- mvs_y[block_idx] = (int16_t)std::round(flow_vec.y * 4.0f);
- }
- }
-}
-
-// Block-based motion compensation with bilinear interpolation (sub-pixel precision)
-// MVs are in 1/4-pixel units
-// This implements the warp() function from MC-EZBC pseudocode
-void warp_block_motion(
- const float *src, // Source frame
- int width, int height,
- const int16_t *mvs_x, // Motion vectors X (1/4-pixel units)
- const int16_t *mvs_y, // Motion vectors Y (1/4-pixel units)
- int block_size, // Block size (e.g., 16)
- float *dst // Output warped frame
-) {
- int num_blocks_x = (width + block_size - 1) / block_size;
- int num_blocks_y = (height + block_size - 1) / block_size;
-
- // Process each block
- for (int by = 0; by < num_blocks_y; by++) {
- for (int bx = 0; bx < num_blocks_x; bx++) {
- int block_idx = by * num_blocks_x + bx;
-
- // Get motion vector for this block (in 1/4-pixel units)
- float mv_x = mvs_x[block_idx] / 4.0f; // Convert to pixels
- float mv_y = mvs_y[block_idx] / 4.0f;
-
- // Block boundaries in destination frame
- int block_x_start = bx * block_size;
- int block_y_start = by * block_size;
- int block_x_end = std::min(block_x_start + block_size, width);
- int block_y_end = std::min(block_y_start + block_size, height);
-
- // Warp each pixel in the block
- for (int y = block_y_start; y < block_y_end; y++) {
- for (int x = block_x_start; x < block_x_end; x++) {
- // Source position (backward warping)
- float src_x = x - mv_x;
- float src_y = y - mv_y;
-
- // Clamp to valid range
- src_x = std::max(0.0f, std::min((float)(width - 1), src_x));
- src_y = std::max(0.0f, std::min((float)(height - 1), src_y));
-
- // Bilinear interpolation
- int x0 = (int)src_x;
- int y0 = (int)src_y;
- int x1 = std::min(x0 + 1, width - 1);
- int y1 = std::min(y0 + 1, height - 1);
-
- float fx = src_x - x0;
- float fy = src_y - y0;
-
- float val00 = src[y0 * width + x0];
- float val10 = src[y0 * width + x1];
- float val01 = src[y1 * width + x0];
- float val11 = src[y1 * width + x1];
-
- float val_top = (1.0f - fx) * val00 + fx * val10;
- float val_bot = (1.0f - fx) * val01 + fx * val11;
- float val = (1.0f - fy) * val_top + fy * val_bot;
-
- dst[y * width + x] = val;
- }
- }
- }
- }
-}
-
-// Bidirectional motion compensation for MC-EZBC predict step
-// Implements: prediction = 0.5 * (warp(f0, MV_fwd) + warp(f1, MV_bwd))
-void warp_bidirectional(
- const float *f0, const float *f1,
- int width, int height,
- const int16_t *mvs_fwd_x, const int16_t *mvs_fwd_y, // F0 → F1
- const int16_t *mvs_bwd_x, const int16_t *mvs_bwd_y, // F1 → F0
- int block_size,
- float *prediction // Output: 0.5 * (warped_f0 + warped_f1)
-) {
- int num_pixels = width * height;
-
- // Allocate temporary buffers
- float *warped_f0 = new float[num_pixels];
- float *warped_f1 = new float[num_pixels];
-
- // Warp f0 forward using forward MVs
- warp_block_motion(f0, width, height, mvs_fwd_x, mvs_fwd_y, block_size, warped_f0);
-
- // Warp f1 backward using backward MVs
- warp_block_motion(f1, width, height, mvs_bwd_x, mvs_bwd_y, block_size, warped_f1);
-
- // Average the two warped frames
- for (int i = 0; i < num_pixels; i++) {
- prediction[i] = 0.5f * (warped_f0[i] + warped_f1[i]);
- }
-
- delete[] warped_f0;
- delete[] warped_f1;
-}
-
-} // extern "C"
diff --git a/video_encoder/encoder_tav_text.c b/video_encoder/encoder_tav_text.c
deleted file mode 100644
index fbddb1f..0000000
--- a/video_encoder/encoder_tav_text.c
+++ /dev/null
@@ -1,795 +0,0 @@
-/*
-encoder_tav_text.c
-Text-based video encoder for TSVM using custom font ROMs
-
-Outputs Videotex files with custom header and packet type 0x3F (text mode)
-
-File structure:
- - Videotex header (32 bytes): magic "\x1FTSVM-VT", version, grid dims, fps, total_frames
- - Extended header packet (0xEF): BGNT, ENDT, CDAT, VNDR, FMPG
- - Font ROM packets (0x30): lowrom and highrom (1920 bytes each)
- - Per-frame sequence: [audio 0x20], [timecode 0xFD], [videotex 0x3F], [sync 0xFF]
-
-Videotex packet structure (0x3F): Zstd([rows][cols][fg-array][bg-array][char-array])
- - rows: uint8 (32)
- - cols: uint8 (80)
- - fg-array: rows*cols bytes (foreground colors, 0xF0=black, 0xFE=white)
- - bg-array: rows*cols bytes (background colors, 0xF0=black, 0xFE=white)
- - char-array: rows*cols bytes (glyph indices 0-255)
-
-Total uncompressed size: 2 + (80*32*3) = 7682 bytes
-Separated arrays compress much better (fg/bg are just 0xF0/0xFE runs)
-Video size: 80×32 characters (560×448 pixels with 7×14 font)
-Audio: MP2 encoding at 96 kbps, 32 KHz stereo (packet 0x20)
-Each text frame is treated as an I-frame with sync packet
-
-Usage:
- gcc -Ofast -std=c11 -Wall encoder_tav_text.c -o encoder_tav_text -lm -lzstd
- ./encoder_tav_text -i video.mp4 -f font.chr -o output.mv3
-*/
-
-#define _POSIX_C_SOURCE 200809L
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-#define ENCODER_VENDOR_STRING "Encoder-TAV-Text 20251121 (videotex)"
-
-#define CHAR_W 7
-#define CHAR_H 14
-#define GRID_W 80
-#define GRID_H 32
-#define PIXEL_W (GRID_W * CHAR_W) // 560
-#define PIXEL_H (GRID_H * CHAR_H) // 448
-#define PATCH_SZ (CHAR_W * CHAR_H)
-#define SAMPLE_RATE 32000
-#define MP2_DEFAULT_PACKET_SIZE 1152
-
-// TAV packet types
-#define PACKET_TIMECODE 0xFD
-#define PACKET_SYNC 0xFF
-#define PACKET_AUDIO_MP2 0x20
-#define PACKET_SSF 0x30
-#define PACKET_TEXT 0x3F
-#define PACKET_EXTENDED_HDR 0xEF
-
-// SSF opcodes for font ROM
-#define SSF_OPCODE_LOWROM 0x80
-#define SSF_OPCODE_HIGHROM 0x81
-
-// Font ROM size constants
-#define FONTROM_PADDED_SIZE 1920
-#define GLYPHS_PER_ROM 128
-
-// Color mapping (4-bit RGB to TSVM palette)
-#define COLOR_BLACK 0xF0
-#define COLOR_WHITE 0xFE
-
-// Generate random filename for temporary audio file
-static void generate_random_filename(char *filename) {
- srand(time(NULL));
-
- const char charset[] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
- const int charset_size = sizeof(charset) - 1;
-
- // Start with the prefix
- strcpy(filename, "/tmp/");
-
- // Generate 32 random characters
- for (int i = 0; i < 32; i++) {
- filename[5 + i] = charset[rand() % charset_size];
- }
-
- // Add the .mp2 extension
- strcpy(filename + 37, ".mp2");
- filename[41] = '\0'; // Null terminate
-}
-
-char TEMP_AUDIO_FILE[42];
-
-// Global flag to disable inverted character matching
-int g_no_invert_char = 0;
-
-typedef struct {
- uint8_t *data; // Binary glyph data (PATCH_SZ bytes per glyph)
- int count; // Number of glyphs
-} FontROM;
-
-// Get FFmpeg version string
-char *get_ffmpeg_version(void) {
- FILE *pipe = popen("ffmpeg -version 2>&1 | head -1", "r");
- if (!pipe) return NULL;
-
- char *version = malloc(256);
- if (!version) {
- pclose(pipe);
- return NULL;
- }
-
- if (fgets(version, 256, pipe)) {
- // Remove trailing newline
- size_t len = strlen(version);
- if (len > 0 && version[len - 1] == '\n') {
- version[len - 1] = '\0';
- }
- pclose(pipe);
- return version;
- }
-
- free(version);
- pclose(pipe);
- return NULL;
-}
-
-// Detect video FPS using ffprobe
-float detect_fps(const char *video_path) {
- char cmd[1024];
- snprintf(cmd, sizeof(cmd),
- "ffprobe -v error -select_streams v:0 -show_entries stream=r_frame_rate "
- "-of default=noprint_wrappers=1:nokey=1 \"%s\" 2>/dev/null",
- video_path);
-
- FILE *pipe = popen(cmd, "r");
- if (!pipe) return 30.0f; // fallback
-
- char fps_str[64] = {0};
- if (fgets(fps_str, sizeof(fps_str), pipe)) {
- // Parse fraction like "30/1" or "24000/1001"
- int num = 0, den = 1;
- if (sscanf(fps_str, "%d/%d", &num, &den) == 2 && den > 0) {
- pclose(pipe);
- return (float)num / (float)den;
- }
- }
- pclose(pipe);
- return 30.0f; // fallback
-}
-
-// Load font ROM (14 bytes per glyph, no header)
-FontROM *load_font_rom(const char *path) {
- FILE *f = fopen(path, "rb");
- if (!f) return NULL;
-
- fseek(f, 0, SEEK_END);
- long size = ftell(f);
- fseek(f, 0, SEEK_SET);
-
- if (size % 14 != 0) {
- fprintf(stderr, "Warning: ROM size not divisible by 14 (got %ld bytes)\n", size);
- }
-
- int glyph_count = size / 14;
- FontROM *rom = malloc(sizeof(FontROM));
- rom->count = glyph_count;
- rom->data = malloc(glyph_count * PATCH_SZ);
-
- // Read and unpack glyphs
- for (int g = 0; g < glyph_count; g++) {
- uint8_t row_bytes[14];
- if (fread(row_bytes, 14, 1, f) != 1) {
- free(rom->data);
- free(rom);
- fclose(f);
- return NULL;
- }
-
- // Unpack bits to binary pixels
- for (int row = 0; row < CHAR_H; row++) {
- for (int col = 0; col < CHAR_W; col++) {
- // Bit 6 = leftmost, bit 0 = rightmost
- int bit = (row_bytes[row] >> (6 - col)) & 1;
- rom->data[g * PATCH_SZ + row * CHAR_W + col] = bit;
- }
- }
- }
-
- fclose(f);
- fprintf(stderr, "Loaded font ROM: %d glyphs\n", glyph_count);
- return rom;
-}
-
-// Find best matching glyph for a grayscale patch
-int find_best_glyph(const uint8_t *patch, const FontROM *rom, uint8_t *out_bg, uint8_t *out_fg) {
- // Try both normal and inverted matching (unless --no-invert-char is set)
- int best_glyph = 0;
- float best_error = INFINITY;
- uint8_t best_bg = COLOR_BLACK, best_fg = COLOR_WHITE;
-
- for (int g = 0; g < rom->count; g++) {
- const uint8_t *glyph = &rom->data[g * PATCH_SZ];
-
- // Try normal: glyph 1 = fg, glyph 0 = bg
- float err_normal = 0;
- for (int i = 0; i < PATCH_SZ; i++) {
- int expected = glyph[i] ? 255 : 0;
- int diff = patch[i] - expected;
- err_normal += diff * diff;
- }
-
- if (err_normal < best_error) {
- best_error = err_normal;
- best_glyph = g;
- best_bg = COLOR_BLACK;
- best_fg = COLOR_WHITE;
- }
-
- // Try inverted: glyph 0 = fg, glyph 1 = bg (skip if --no-invert-char)
- if (!g_no_invert_char) {
- float err_inverted = 0;
- for (int i = 0; i < PATCH_SZ; i++) {
- int expected = glyph[i] ? 0 : 255;
- int diff = patch[i] - expected;
- err_inverted += diff * diff;
- }
-
- if (err_inverted < best_error) {
- best_error = err_inverted;
- best_glyph = g;
- best_bg = COLOR_WHITE;
- best_fg = COLOR_BLACK;
- }
- }
- }
-
- *out_bg = best_bg;
- *out_fg = best_fg;
- return best_glyph;
-}
-
-// Convert frame to text mode
-void frame_to_text(const uint8_t *pixels, const FontROM *rom,
- uint8_t *bg_col, uint8_t *fg_col, uint8_t *chars) {
- uint8_t patch[PATCH_SZ];
-
- for (int gr = 0; gr < GRID_H; gr++) {
- for (int gc = 0; gc < GRID_W; gc++) {
- int idx = gr * GRID_W + gc;
-
- // Extract patch
- for (int y = 0; y < CHAR_H; y++) {
- for (int x = 0; x < CHAR_W; x++) {
- int px = gc * CHAR_W + x;
- int py = gr * CHAR_H + y;
- patch[y * CHAR_W + x] = pixels[py * PIXEL_W + px];
- }
- }
-
- // Find best match
- chars[idx] = find_best_glyph(patch, rom, &bg_col[idx], &fg_col[idx]);
- }
- }
-}
-
-// Get current time in nanoseconds since UNIX epoch
-uint64_t get_current_time_ns(void) {
- struct timeval tv;
- gettimeofday(&tv, NULL);
- return (uint64_t)tv.tv_sec * 1000000000ULL + (uint64_t)tv.tv_usec * 1000ULL;
-}
-
-// Parse MP2 packet header to get accurate packet size
-int get_mp2_packet_size(uint8_t *header) {
- int bitrate_index = (header[2] >> 4) & 0x0F;
- int bitrates[] = {0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384};
- if (bitrate_index >= 15) return MP2_DEFAULT_PACKET_SIZE;
-
- int bitrate = bitrates[bitrate_index];
- if (bitrate == 0) return MP2_DEFAULT_PACKET_SIZE;
-
- int sampling_freq_index = (header[2] >> 2) & 0x03;
- int sampling_freqs[] = {44100, 48000, 32000, 0};
- int sampling_freq = sampling_freqs[sampling_freq_index];
- if (sampling_freq == 0) return MP2_DEFAULT_PACKET_SIZE;
-
- int padding = (header[2] >> 1) & 0x01;
- return (144 * bitrate * 1000) / sampling_freq + padding;
-}
-
-// Write Videotex header (32 bytes, similar to TAV but simpler)
-void write_videotex_header(FILE *f, uint8_t fps, uint32_t total_frames) {
- fwrite("\x1FTSVMTAV", 8, 1, f);
-
- // Version: 1 (uint8)
- fputc(1, f);
-
- // Grid dimensions (uint8 each)
- uint16_t width = GRID_W;
- uint16_t height = GRID_H;
- fwrite(&width, sizeof(uint16_t), 1, f); // cols = 80
- fwrite(&height, sizeof(uint16_t), 1, f); // rows = 32
-
- // FPS (uint8)
- fputc(fps, f);
-
- // Total frames (uint32, little-endian)
- fwrite(&total_frames, sizeof(uint32_t), 1, f);
-
- fputc(0, f); // wavelet filter type
- fputc(0, f); // decomposition levels
- fputc(0, f); // quantiser Y
- fputc(0, f); // quantiser Co
- fputc(0, f); // quantiser Cg
-
- // Feature Flags
- fputc(0x03, f); // bit 0 = has audio; bit 1 = has subtitle (Videotex is classified as subtitles)
-
- // Video Flags
- fputc(0x80, f); // bit 7 = has no video (Videotex is classified as subtitles)
-
-
- fputc(0, f); // encoder quality level
- fputc(0x02, f); // channel layout: Y only
- fputc(0, f); // entropy coder
-
- fputc(0, f); // reserved
- fputc(0, f); // reserved
-
- fputc(0, f); // device orientation: no rotation
- fputc(0, f); // file role: generic
-}
-
-// Write extended header packet with metadata
-// Returns the file offset where ENDT value is written (for later update)
-long write_extended_header(FILE *f, uint64_t creation_time_ns, const char *ffmpeg_version) {
- fputc(PACKET_EXTENDED_HDR, f);
-
- // Helper macros for key-value pairs
- #define WRITE_KV_UINT64(key_str, value) do { \
- fwrite(key_str, 1, 4, f); \
- uint8_t value_type = 0x04; /* Uint64 */ \
- fwrite(&value_type, 1, 1, f); \
- uint64_t val = (value); \
- fwrite(&val, sizeof(uint64_t), 1, f); \
- } while(0)
-
- #define WRITE_KV_BYTES(key_str, data, len) do { \
- fwrite(key_str, 1, 4, f); \
- uint8_t value_type = 0x10; /* Bytes */ \
- fwrite(&value_type, 1, 1, f); \
- uint16_t length = (len); \
- fwrite(&length, sizeof(uint16_t), 1, f); \
- fwrite((data), 1, (len), f); \
- } while(0)
-
- // Count key-value pairs (BGNT, ENDT, CDAT, VNDR, FMPG)
- uint16_t num_pairs = ffmpeg_version ? 5 : 4; // FMPG is optional
- fwrite(&num_pairs, sizeof(uint16_t), 1, f);
-
- // BGNT: Video begin time (0 for frame 0)
- WRITE_KV_UINT64("BGNT", 0ULL);
-
- // ENDT: Video end time (placeholder, will be updated at end)
- long endt_offset = ftell(f);
- WRITE_KV_UINT64("ENDT", 0ULL);
-
- // CDAT: Creation time in nanoseconds since UNIX epoch
- WRITE_KV_UINT64("CDAT", creation_time_ns);
-
- // VNDR: Encoder name and version
- const char *vendor_str = ENCODER_VENDOR_STRING;
- WRITE_KV_BYTES("VNDR", vendor_str, strlen(vendor_str));
-
- // FMPG: FFmpeg version (if available)
- if (ffmpeg_version) {
- WRITE_KV_BYTES("FMPG", ffmpeg_version, strlen(ffmpeg_version));
- }
-
- #undef WRITE_KV_UINT64
- #undef WRITE_KV_BYTES
-
- // Return offset of ENDT value (skip key, type byte)
- return endt_offset + 4 + 1; // 4 bytes for "ENDT", 1 byte for type
-}
-
-// Write font ROM packet (SSF packet type 0x30)
-void write_fontrom_packet(FILE *f, const uint8_t *rom_data, size_t data_size, uint8_t opcode) {
- // Prepare padded ROM data (pad to FONTROM_PADDED_SIZE with zeros)
- uint8_t *padded_data = calloc(1, FONTROM_PADDED_SIZE);
- memcpy(padded_data, rom_data, data_size);
-
- // Packet structure:
- // [type:0x30][size:uint32][index:uint24][opcode:uint8][length:uint16][data][terminator:0x00]
- uint32_t packet_size = 3 + 1 + 2 + FONTROM_PADDED_SIZE + 1;
-
- // Write packet type and size
- fputc(PACKET_SSF, f);
- fwrite(&packet_size, sizeof(uint32_t), 1, f);
-
- // Write SSF payload
- // Index (3 bytes, always 0 for font ROM)
- fputc(0, f);
- fputc(0, f);
- fputc(0, f);
-
- // Opcode (0x80=lowrom, 0x81=highrom)
- fputc(opcode, f);
-
- // Payload length (uint16, little-endian)
- uint16_t payload_len = FONTROM_PADDED_SIZE;
- fwrite(&payload_len, sizeof(uint16_t), 1, f);
-
- // Font data (padded to 1920 bytes)
- fwrite(padded_data, 1, FONTROM_PADDED_SIZE, f);
-
- // Terminator
- fputc(0x00, f);
-
- free(padded_data);
-
- fprintf(stderr, "Font ROM uploaded: %zu bytes (padded to %d), opcode 0x%02X\n",
- data_size, FONTROM_PADDED_SIZE, opcode);
-}
-
-// Write timecode packet (nanoseconds)
-void write_timecode(FILE *f, uint64_t timecode_ns) {
- fputc(PACKET_TIMECODE, f);
- fwrite(&timecode_ns, sizeof(uint64_t), 1, f);
-}
-
-// Write sync packet
-void write_sync(FILE *f) {
- fputc(PACKET_SYNC, f);
-}
-
-// Write MP2 audio packet
-void write_audio_mp2(FILE *f, const uint8_t *data, uint32_t size) {
- fputc(PACKET_AUDIO_MP2, f);
- fwrite(&size, sizeof(uint32_t), 1, f);
- fwrite(data, 1, size, f);
-}
-
-// Write text packet with separated arrays (better compression)
-void write_text_packet(FILE *f, const uint8_t *bg_col, const uint8_t *fg_col,
- const uint8_t *chars, int rows, int cols) {
- int grid_size = rows * cols;
-
- // Prepare uncompressed data: [rows][cols][fg-array][bg-array][char-array]
- // Separated arrays compress much better (fg/bg are just 0xF0/0xFE runs)
- size_t uncompressed_size = 2 + grid_size * 3;
- uint8_t *uncompressed = malloc(uncompressed_size);
-
- uncompressed[0] = rows;
- uncompressed[1] = cols;
-
- // Copy arrays in order: foreground, background, characters
- memcpy(&uncompressed[2], fg_col, grid_size); // Foreground first
- memcpy(&uncompressed[2 + grid_size], bg_col, grid_size); // Background second
- memcpy(&uncompressed[2 + grid_size * 2], chars, grid_size); // Characters third
-
- // Compress with Zstd
- size_t max_compressed = ZSTD_compressBound(uncompressed_size);
- uint8_t *compressed = malloc(max_compressed);
- size_t compressed_size = ZSTD_compress(compressed, max_compressed,
- uncompressed, uncompressed_size, 3);
-
- if (ZSTD_isError(compressed_size)) {
- fprintf(stderr, "Zstd compression error\n");
- exit(1);
- }
-
- // Write packet: [type][size][data]
- fputc(PACKET_TEXT, f);
- uint32_t size32 = compressed_size;
- fwrite(&size32, 4, 1, f);
- fwrite(compressed, compressed_size, 1, f);
-
- free(compressed);
- free(uncompressed);
-}
-
-int main(int argc, char **argv) {
- if (argc < 7) {
- fprintf(stderr, "Usage: %s -i