mirror of
https://github.com/curioustorvald/tsvm.git
synced 2026-03-07 19:51:51 +09:00
tav playing concatenated video streams (fixed)
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user