tav playing concatenated video streams (fixed)

This commit is contained in:
minjaesong
2025-09-24 00:41:49 +09:00
parent a9ba57c09a
commit 0b3497b013
2 changed files with 60 additions and 70 deletions

View File

@@ -8,7 +8,6 @@
const WIDTH = 560
const HEIGHT = 448
const TILE_SIZE = 112 // 112x112 tiles for DWT (perfect fit for TSVM 560x448 resolution)
const TAV_MAGIC = [0x1F, 0x54, 0x53, 0x56, 0x4D, 0x54, 0x41, 0x56] // "\x1FTSVM TAV"
const TAV_VERSION = 1 // Initial DWT version
const SND_BASE_ADDR = audio.getBaseAddr()
@@ -27,6 +26,7 @@ const TAV_PACKET_PFRAME = 0x11
const TAV_PACKET_AUDIO_MP2 = 0x20
const TAV_PACKET_SUBTITLE = 0x30
const TAV_PACKET_SYNC = 0xFF
const TAV_FILE_HEADER_FIRST = 0x1F
// Wavelet filter types
const WAVELET_5_3_REVERSIBLE = 0
@@ -47,21 +47,12 @@ let subtitlePosition = 0 // 0=bottom center (default)
// Parse command line options
let interactive = false
let debugMotionVectors = false
let deinterlaceAlgorithm = "yadif"
let enableDeblocking = false // Default: disabled (use -deblock to enable)
if (exec_args.length > 2) {
for (let i = 2; i < exec_args.length; i++) {
const arg = exec_args[i].toLowerCase()
if (arg === "-i") {
interactive = true
} else if (arg === "-debug-mv") {
debugMotionVectors = true
} else if (arg === "-deblock") {
enableDeblocking = true
} else if (arg.startsWith("-deinterlace=")) {
deinterlaceAlgorithm = arg.substring(13)
}
}
}
@@ -441,9 +432,9 @@ const isNTSC = (header.videoFlags & 0x02) !== 0
const isLossless = (header.videoFlags & 0x04) !== 0
// Calculate tile dimensions (112x112 vs TEV's 16x16 blocks)
const tilesX = Math.ceil(header.width / TILE_SIZE)
const tilesY = Math.ceil(header.height / TILE_SIZE)
const numTiles = tilesX * tilesY
const tilesX = Math.ceil(header.width / 2)
const tilesY = Math.ceil(header.height / 2)
const numTiles = 4
console.log(`TAV Decoder`)
console.log(`Resolution: ${header.width}x${header.height}`)
@@ -467,12 +458,6 @@ const RGB_BUFFER_B = sys.malloc(FRAME_SIZE)
let CURRENT_RGB_ADDR = RGB_BUFFER_A
let PREV_RGB_ADDR = RGB_BUFFER_B
// Motion vector storage
let motionVectors = new Array(numTiles)
for (let i = 0; i < numTiles; i++) {
motionVectors[i] = { mvX: 0, mvY: 0, rcf: 1.0 }
}
// Audio state
let audioBufferBytesLastFrame = 0
let frame_cnt = 0
@@ -572,21 +557,21 @@ function tryReadNextTAVHeader() {
let currentPos = seqread.getReadCount()
// Try to read magic number
let newMagic = new Array(8)
let newMagic = new Array(7)
try {
for (let i = 0; i < 8; i++) {
for (let i = 0; i < newMagic.length; i++) {
newMagic[i] = seqread.readOneByte()
}
// compensating the old encoder emitting extra sync packets
while (newMagic[0] == 255) {
newMagic.shift(); newMagic[7] = seqread.readOneByte()
newMagic.shift(); newMagic[newMagic.length - 1] = seqread.readOneByte()
}
// Check if it matches TAV magic
let isValidTAV = true
for (let i = 0; i < 8; i++) {
if (newMagic[i] !== TAV_MAGIC[i]) {
for (let i = 0; i < newMagic.length; i++) {
if (newMagic[i] !== TAV_MAGIC[i+1]) {
isValidTAV = false
serial.printerr("Header mismatch: got "+newMagic.join())
break
@@ -594,6 +579,8 @@ function tryReadNextTAVHeader() {
}
if (isValidTAV) {
serial.println("Got next video file")
// Read the rest of the header
let newHeader = {
magic: newMagic,
@@ -612,6 +599,8 @@ function tryReadNextTAVHeader() {
reserved: new Array(7)
}
serial.println("File header: " + JSON.stringify(newHeader))
// Skip reserved bytes
for (let i = 0; i < 7; i++) {
seqread.readOneByte()
@@ -621,6 +610,7 @@ function tryReadNextTAVHeader() {
}
} catch (e) {
serial.printerr(e)
// EOF or read error - restore position and return null
// Note: seqread doesn't have seek, so we can't restore position
// This is okay since we're at EOF anyway
@@ -635,47 +625,7 @@ try {
let totalFilesProcessed = 0
while (!stopPlay && seqread.getReadCount() < FILE_LENGTH) {
// Check if we've finished the current file
if (header.totalFrames > 0 && frameCount >= header.totalFrames) {
console.log(`Completed file ${currentFileIndex}: ${frameCount} frames`)
// Try to read next TAV file header
let nextHeader = tryReadNextTAVHeader()
if (nextHeader) {
// Found another TAV file - update header and reset counters
header = nextHeader
frameCount = 0
akku = 0.0
akku2 = 0.0
FRAME_TIME = 1.0 / header.fps
currentFileIndex++
totalFilesProcessed++
console.log(`\nStarting file ${currentFileIndex}:`)
console.log(`Resolution: ${header.width}x${header.height}`)
console.log(`FPS: ${header.fps}`)
console.log(`Total frames: ${header.totalFrames}`)
console.log(`Wavelet filter: ${header.waveletFilter === WAVELET_5_3_REVERSIBLE ? "5/3 reversible" : "9/7 irreversible"}`)
console.log(`Quality: Y=${header.qualityY}, Co=${header.qualityCo}, Cg=${header.qualityCg}`)
// Reset motion vectors for new file
for (let i = 0; i < numTiles; i++) {
motionVectors[i] = { mvX: 0, mvY: 0, rcf: 1.0 }
}
// Continue with new file
continue
} else {
// No more TAV files found
console.log(`\nNo more TAV files found. Total files processed: ${currentFileIndex}`)
break
}
}
// Original playback loop condition (but without totalFrames check since we handle it above)
if (seqread.getReadCount() >= FILE_LENGTH) {
break
}
// Handle interactive controls
if (interactive) {
@@ -688,7 +638,34 @@ try {
if (akku >= FRAME_TIME) {
// Read packet header
const packetType = seqread.readOneByte()
var packetType = seqread.readOneByte()
// Try to read next TAV file header
if (packetType == TAV_FILE_HEADER_FIRST) {
let nextHeader = tryReadNextTAVHeader()
if (nextHeader) {
// Found another TAV file - update header and reset counters
header = nextHeader
frameCount = 0
akku = 0.0
akku2 = 0.0
FRAME_TIME = 1.0 / header.fps
currentFileIndex++
totalFilesProcessed++
console.log(`\nStarting file ${currentFileIndex}:`)
console.log(`Resolution: ${header.width}x${header.height}`)
console.log(`FPS: ${header.fps}`)
console.log(`Total frames: ${header.totalFrames}`)
console.log(`Wavelet filter: ${header.waveletFilter === WAVELET_5_3_REVERSIBLE ? "5/3 reversible" : "9/7 irreversible"}`)
console.log(`Quality: Y=${header.qualityY}, Co=${header.qualityCo}, Cg=${header.qualityCg}`)
// Continue with new file
packetType = seqread.readOneByte()
}
else
break
}
if (packetType === TAV_PACKET_SYNC) {
// Sync packet - no additional data
@@ -701,7 +678,8 @@ try {
CURRENT_RGB_ADDR = PREV_RGB_ADDR
PREV_RGB_ADDR = temp
} else if (packetType === TAV_PACKET_IFRAME || packetType === TAV_PACKET_PFRAME) {
}
else if (packetType === TAV_PACKET_IFRAME || packetType === TAV_PACKET_PFRAME) {
// Video packet
const compressedSize = seqread.readInt()
const isKeyframe = (packetType === TAV_PACKET_IFRAME)
@@ -779,7 +757,8 @@ try {
console.log(`Frame ${frameCount}: Decompress=${decompressTime.toFixed(1)}ms, Decode=${decodeTime.toFixed(1)}ms, Upload=${uploadTime.toFixed(1)}ms, Bias=${biasTime.toFixed(1)}ms, Total=${totalTime.toFixed(1)}ms`)
}
} else if (packetType === TAV_PACKET_AUDIO_MP2) {
}
else if (packetType === TAV_PACKET_AUDIO_MP2) {
// MP2 Audio packet
let audioLen = seqread.readInt()
@@ -792,7 +771,8 @@ try {
audio.mp2Decode()
audio.mp2UploadDecoded(0)
} else if (packetType === TAV_PACKET_SUBTITLE) {
}
else if (packetType === TAV_PACKET_SUBTITLE) {
// Subtitle packet - same format as TEV
let packetSize = seqread.readInt()
processSubtitlePacket(packetSize)

View File

@@ -452,6 +452,7 @@ Packet Types -
<special>
255,255: sync packet (wait until the next frame)
254,255: background colour packet
31,84 : prohibited
Packet Type High Byte (iPF Type Numbers)
0..7: iPF Type 1..8
@@ -717,6 +718,7 @@ DCT-based compression, motion compensation, and efficient temporal coding.
0x20: MP2 audio packet
0x30: Subtitle in "Simple" format
0xFF: sync packet
0x1F: prohibited
## Video Packet Structure
uint8 Packet Type
@@ -821,7 +823,9 @@ transmission capability, and region-of-interest coding.
uint16 Height: video height in pixels
uint8 FPS: frames per second
uint32 Total Frames: number of video frames
uint8 Wavelet Filter Type: 0=5/3 reversible, 1=9/7 irreversible
uint8 Wavelet Filter Type/File Role:
- 0 = 5/3 reversible
- 1 = 9/7 irreversible
uint8 Decomposition Levels: number of DWT levels (1-4)
uint8 Quantiser Index for Y channel (1: lossless, 255: potato)
uint8 Quantiser Index for Co channel (1: lossless, 255: potato)
@@ -830,9 +834,14 @@ transmission capability, and region-of-interest coding.
- bit 0 = has audio
- bit 1 = has subtitle
uint8 Video Flags
- bit 0 = is interlaced (unused)
- bit 0 = has no actual packets, this file is header-only without an Intro Movie
- bit 1 = is NTSC framerate
- bit 2 = is lossless mode
uint8 File Role
- 0 = generic
- 1 = this file is header-only, and UCF payload will be followed (used by seekable movie file)
When header-only file contain video packets, they should be presented as an Intro Movie
before the user-interactable selector (served by the UCF payoad)
uint8 Reserved[7]: fill with zeros
## Packet Types
@@ -841,6 +850,7 @@ transmission capability, and region-of-interest coding.
0x20: MP2 audio packet
0x30: Subtitle in "Simple" format
0xFF: sync packet
0x1F: prohibited
## Video Packet Structure
uint8 Packet Type