From d3058d515d259c477ee3a9ad8e8b81d9d71050cf Mon Sep 17 00:00:00 2001 From: minjaesong Date: Tue, 3 May 2022 14:06:50 +0900 Subject: [PATCH] movie en/decoding using ipf1/2 formats --- assets/disk0/decodeipf2.js | 8 +- assets/disk0/decodemovipf.js | 183 ++++++++++++++++++++++++++++++++++ assets/disk0/encodemovipf.js | 69 +++++++++++++ assets/disk0/encodemovipf2.js | 69 +++++++++++++ terranmon.txt | 14 +-- 5 files changed, 329 insertions(+), 14 deletions(-) create mode 100644 assets/disk0/decodemovipf.js create mode 100644 assets/disk0/encodemovipf.js create mode 100644 assets/disk0/encodemovipf2.js diff --git a/assets/disk0/decodeipf2.js b/assets/disk0/decodeipf2.js index 453d760..b2906fc 100644 --- a/assets/disk0/decodeipf2.js +++ b/assets/disk0/decodeipf2.js @@ -153,10 +153,10 @@ function ycocgToRGB(co1, co2, cg1, cg2, ys, as) { // ys: 4 Y-values // cocg = 0x7777 // ys = 0x7777 - co1 = (co1 - 7) / 8 - co2 = (co2 - 7) / 8 - cg1 = (cg1 - 7) / 8 - cg2 = (cg2 - 7) / 8 + co1 = (co1 - 7) / 8.0 + co2 = (co2 - 7) / 8.0 + cg1 = (cg1 - 7) / 8.0 + cg2 = (cg2 - 7) / 8.0 let y1 = (ys & 15) / 15.0 let a1 = as & 15 diff --git a/assets/disk0/decodemovipf.js b/assets/disk0/decodemovipf.js new file mode 100644 index 0000000..3e2ba7a --- /dev/null +++ b/assets/disk0/decodemovipf.js @@ -0,0 +1,183 @@ + +let filename = exec_args[1] + +const FBUF_SIZE = 560*448 +const MAGIC = [0x1F, 0x54, 0x53, 0x56, 0x4D, 0x4D, 0x4F, 0x56] +const port = filesystem._toPorts("A")[0] + +println("Reading...") + +com.sendMessage(port, "DEVRST\x17") +com.sendMessage(port, `OPENR"${filename}",1`) +let statusCode = com.getStatusCode(port) + +if (statusCode != 0) { + printerrln(`No such file (${statusCode})`) + return statusCode +} + +com.sendMessage(port, "READ") +statusCode = com.getStatusCode(port) +if (statusCode != 0) { + printerrln("READ failed with "+statusCode) + return statusCode +} + +con.clear(); con.curs_set(0) + +let readCount = 0 + +function readBytes(length) { + let ptr = sys.malloc(length) + let requiredBlocks = Math.floor((readCount + length) / 4096) - Math.floor(readCount / 4096) + + let completedReads = 0 + + //serial.println(`readBytes(${length}); readCount = ${readCount}`) + + for (let bc = 0; bc < requiredBlocks + 1; bc++) { + if (completedReads >= length) break + + if (readCount % 4096 == 0) { + //serial.println("READ from serial") + // pull the actual message + sys.poke(-4093 - port, 6);sys.sleep(0) // spinning is required as Graal run is desynced with the Java side + + let blockTransferStatus = ((sys.peek(-4085 - port*2) & 255) | ((sys.peek(-4086 - port*2) & 255) << 8)) + let thisBlockLen = blockTransferStatus & 4095 + if (thisBlockLen == 0) thisBlockLen = 4096 // [1, 4096] + let hasMore = (blockTransferStatus & 0x8000 != 0) + + + //serial.println(`block: (${thisBlockLen})[${[...Array(thisBlockLen).keys()].map(k => (sys.peek(-4097 - k) & 255).toString(16).padStart(2,'0')).join()}]`) + + let remaining = Math.min(thisBlockLen, length - completedReads) + + //serial.println(`Pulled a block (${thisBlockLen}); readCount = ${readCount}, completedReads = ${completedReads}, remaining = ${remaining}`) + + // copy from read buffer to designated position + sys.memcpy(-4097, ptr + completedReads, remaining) + + // increment readCount properly + readCount += remaining + completedReads += remaining + } + else { + let padding = readCount % 4096 + let remaining = length - completedReads + let thisBlockLen = Math.min(4096 - padding, length - completedReads) + + //serial.println(`padding = ${padding}; remaining = ${remaining}`) + + //serial.println(`block: (${thisBlockLen})[${[...Array(thisBlockLen).keys()].map(k => (sys.peek(-4097 - padding - k) & 255).toString(16).padStart(2,'0')).join()}]`) + + //serial.println(`Reusing a block (${thisBlockLen}); readCount = ${readCount}, completedReads = ${completedReads}`) + + // copy from read buffer to designated position + sys.memcpy(-4097 - padding, ptr + completedReads, thisBlockLen) + + // increment readCount properly + readCount += thisBlockLen + completedReads += thisBlockLen + } + } + + //serial.println(`END readBytes(${length}); readCount = ${readCount}\n`) + + return ptr +} + +function readInt() { + let b = readBytes(4) + let i = (sys.peek(b) & 255) | ((sys.peek(b+1) & 255) << 8) | ((sys.peek(b+2) & 255) << 16) | ((sys.peek(b+3) & 255) << 24) + + //serial.println(`readInt(); bytes: ${sys.peek(b)}, ${sys.peek(b+1)}, ${sys.peek(b+2)}, ${sys.peek(b+3)} = ${i}\n`) + + sys.free(b) + return i +} + +function readShort() { + let b = readBytes(2) + let i = (sys.peek(b) & 255) | ((sys.peek(b+1) & 255) << 8) + + //serial.println(`readShort(); bytes: ${sys.peek(b)}, ${sys.peek(b+1)} = ${i}\n`) + + sys.free(b) + return i +} + + +let magic = readBytes(8) +let magicMatching = true + +// check if magic number matches +MAGIC.forEach((b,i) => { + let testb = sys.peek(magic + i) & 255 // for some reason this must be located here + if (testb != b) { + magicMatching = false + } +}) +sys.free(magic) +if (!magicMatching) { + println("Not a movie file (MAGIC mismatch)") + return 1 +} + + +let width = readShort() +let height = readShort() +let fps = readShort(); if (fps == 0) fps = 9999 +let frameTime = 1.0 / fps +let frameCount = readInt() % 16777216 +let type = readShort() +sys.free(readBytes(12)) // skip 12 bytes +let akku = frameTime +let framesRendered = 0 +//serial.println(readCount) // must say 18 +//serial.println(`Dim: (${width}x${height}), FPS: ${fps}, Frames: ${frameCount}`) + +if (type != 4 && type != 5 && type != 260 && type != 261) { + printerrln("Not a type 4/5 mov") + return 1 +} + +let ipfbuf = sys.malloc(FBUF_SIZE) +graphics.setGraphicsMode(4) + +let fb1 = sys.malloc(FBUF_SIZE) +let fb2 = sys.malloc(FBUF_SIZE) + +let startTime = sys.nanoTime() + +let decodefun = (type > 255) ? graphics.decodeIpf2 : graphics.decodeIpf1 + +while (framesRendered < frameCount) { + //serial.println(`Frame #${f+1}`) + + let t1 = sys.nanoTime() + + if (akku >= frameTime) { + akku -= frameTime + + let payloadLen = readInt() + let gzippedPtr = readBytes(payloadLen) + + gzip.decompFromTo(gzippedPtr, payloadLen, ipfbuf) // should return FBUF_SIZE + decodefun(ipfbuf, -1048577, -1310721, width, height, (type & 255) == 5) + sys.free(gzippedPtr) + + framesRendered += 1 + } + sys.sleep(1) + + let t2 = sys.nanoTime() + akku += (t2 - t1) / 1000000000.0 +} +let endTime = sys.nanoTime() + +sys.free(ipfbuf) + +let timeTook = (endTime - startTime) / 1000000000.0 + +//println(`Actual FPS: ${frameCount / timeTook}`) \ No newline at end of file diff --git a/assets/disk0/encodemovipf.js b/assets/disk0/encodemovipf.js new file mode 100644 index 0000000..d682e1d --- /dev/null +++ b/assets/disk0/encodemovipf.js @@ -0,0 +1,69 @@ + +const FBUF_SIZE = 560*448 +let infile = sys.malloc(120000) // somewhat arbitrary +let imagearea = sys.malloc(FBUF_SIZE*3) +let decodearea = sys.malloc(FBUF_SIZE) +let ipfarea = sys.malloc(FBUF_SIZE) +let gzippedImage = sys.malloc(180000) // somewhat arbitrary + +let outfilename = exec_args[1] + +if (!outfilename) return 1 + +function appendToOutfile(bytes) { + filesystem.open("A", outfilename, "A") + filesystem.writeBytes("A", bytes) +} + +function appendToOutfilePtr(ptr, len) { + filesystem.open("A", outfilename, "A") + dma.ramToCom(ptr, 0, len) +} + +// write header to the file +let headerBytes = [ + 0x1F, 0x54, 0x53, 0x56, 0x4D, 0x4D, 0x4F, 0x56, // magic + 0x30, 0x02, // width (560) + 0xC0, 0x01, // height (448) + 0x1E, 0x00, // FPS (30) + 0x34, 0x00, 0x00, 0x00, // frame count (52) + 0x04, 0x00, // type 4 frames (force no-alpha) + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // reserved +] + +filesystem.open("A", outfilename, "W") +filesystem.writeBytes("A", headerBytes) + +for (let f = 1; f <=52; f++) { + let fname = `/movtestimg/${(''+f).padStart(3,'0')}.jpg` + filesystem.open("A", fname, "R") + let fileLen = filesystem.getFileLen("A") + dma.comToRam(0, 0, infile, fileLen) + + graphics.decodeImageTo(infile, fileLen, imagearea) + + print(`Encoding frame ${f}...`) + +// graphics.imageToDisplayableFormat(imagearea, decodearea, 560, 448, 3, 1) + graphics.encodeIpf1(imagearea, ipfarea, 560, 448, 3, false, f) + + let gzlen = gzip.compFromTo(ipfarea, FBUF_SIZE, gzippedImage) + + let frameSize = [ + (gzlen >>> 0) & 255, + (gzlen >>> 8) & 255, + (gzlen >>> 16) & 255, + (gzlen >>> 24) & 255 + ] + + appendToOutfile(frameSize) + appendToOutfilePtr(gzippedImage, gzlen) + + print(` ${gzlen} bytes\n`) +} + +sys.free(infile) +sys.free(imagearea) +sys.free(decodearea) +sys.free(ipfarea) +sys.free(gzippedImage) \ No newline at end of file diff --git a/assets/disk0/encodemovipf2.js b/assets/disk0/encodemovipf2.js new file mode 100644 index 0000000..f2e103e --- /dev/null +++ b/assets/disk0/encodemovipf2.js @@ -0,0 +1,69 @@ + +const FBUF_SIZE = 560*448 +let infile = sys.malloc(120000) // somewhat arbitrary +let imagearea = sys.malloc(FBUF_SIZE*3) +let decodearea = sys.malloc(FBUF_SIZE) +let ipfarea = sys.malloc(FBUF_SIZE) +let gzippedImage = sys.malloc(180000) // somewhat arbitrary + +let outfilename = exec_args[1] + +if (!outfilename) return 1 + +function appendToOutfile(bytes) { + filesystem.open("A", outfilename, "A") + filesystem.writeBytes("A", bytes) +} + +function appendToOutfilePtr(ptr, len) { + filesystem.open("A", outfilename, "A") + dma.ramToCom(ptr, 0, len) +} + +// write header to the file +let headerBytes = [ + 0x1F, 0x54, 0x53, 0x56, 0x4D, 0x4D, 0x4F, 0x56, // magic + 0x30, 0x02, // width (560) + 0xC0, 0x01, // height (448) + 0x1E, 0x00, // FPS (30) + 0x34, 0x00, 0x00, 0x00, // frame count (52) + 0x04, 0x01, // type 4 frames (force no-alpha) + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // reserved +] + +filesystem.open("A", outfilename, "W") +filesystem.writeBytes("A", headerBytes) + +for (let f = 1; f <=52; f++) { + let fname = `/movtestimg/${(''+f).padStart(3,'0')}.jpg` + filesystem.open("A", fname, "R") + let fileLen = filesystem.getFileLen("A") + dma.comToRam(0, 0, infile, fileLen) + + graphics.decodeImageTo(infile, fileLen, imagearea) + + print(`Encoding frame ${f}...`) + +// graphics.imageToDisplayableFormat(imagearea, decodearea, 560, 448, 3, 1) + graphics.encodeIpf2(imagearea, ipfarea, 560, 448, 3, false, f) + + let gzlen = gzip.compFromTo(ipfarea, FBUF_SIZE, gzippedImage) + + let frameSize = [ + (gzlen >>> 0) & 255, + (gzlen >>> 8) & 255, + (gzlen >>> 16) & 255, + (gzlen >>> 24) & 255 + ] + + appendToOutfile(frameSize) + appendToOutfilePtr(gzippedImage, gzlen) + + print(` ${gzlen} bytes\n`) +} + +sys.free(infile) +sys.free(imagearea) +sys.free(decodearea) +sys.free(ipfarea) +sys.free(gzippedImage) \ No newline at end of file diff --git a/terranmon.txt b/terranmon.txt index 17d1b9b..b7b73a0 100644 --- a/terranmon.txt +++ b/terranmon.txt @@ -367,23 +367,17 @@ Type: 0: 256-Colour frame 1: 256-Colour frame with palette data 2: 4096-Colour frame (stored as two byte-planes) - 3: iPF indicator (see iPF Type Numbers for details) + 4: iPF no-alpha indicator (see iPF Type Numbers for details) + 5: iPF with alpha indicator (see iPF Type Numbers for details) 16: Series of JPEGs 18: Series of PNGs 20: Series of TGAs 21: Series of TGA/GZs 255: Every frame specifies the type - iPF Type Numbers + iPF Type Numbers (high bytes) - 3: iPF Type 1 - 259: iPF Type 2 - 515: iPF Type 3 - 771: iPF Type 4 - 1027: iPF Type 5 - 1283: iPF Type 6 - 1539: iPF Type 7 - 1795: iPF Type 8 + 0..7: iPF Type 1..8 TYPE 0 FRAME -