mirror of
https://github.com/curioustorvald/tsvm.git
synced 2026-06-09 06:34:04 +09:00
fix: FPS conversion not changing video header
This commit is contained in:
21
assets/disk0/tvdos/bin/autorun.js
Normal file
21
assets/disk0/tvdos/bin/autorun.js
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
const fullFilePath = _G.shell.resolvePathInput(exec_args[1])
|
||||||
|
|
||||||
|
let seqread = undefined
|
||||||
|
let fullFilePathStr = fullFilePath.full
|
||||||
|
let mode = ""
|
||||||
|
|
||||||
|
// Select seqread driver to use
|
||||||
|
if (fullFilePathStr.startsWith('$:/TAPE') || fullFilePathStr.startsWith('$:\\TAPE')) {
|
||||||
|
seqread = require("seqreadtape")
|
||||||
|
seqread.prepare(fullFilePathStr)
|
||||||
|
seqread.seek(0)
|
||||||
|
mode = "tape"
|
||||||
|
} else {
|
||||||
|
seqread = undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if ("tape" == mode) {
|
||||||
|
const prg = seqread.readString(65536)
|
||||||
|
eval(prg)
|
||||||
|
}
|
||||||
@@ -60,13 +60,13 @@ let fullFilePathStr = fullFilePath.full
|
|||||||
// Select seqread driver to use
|
// Select seqread driver to use
|
||||||
if (fullFilePathStr.startsWith('$:/TAPE') || fullFilePathStr.startsWith('$:\\TAPE')) {
|
if (fullFilePathStr.startsWith('$:/TAPE') || fullFilePathStr.startsWith('$:\\TAPE')) {
|
||||||
seqread = seqreadtape
|
seqread = seqreadtape
|
||||||
|
seqread.prepare(fullFilePathStr)
|
||||||
seqread.seek(0)
|
seqread.seek(0)
|
||||||
} else {
|
} else {
|
||||||
seqread = seqreadserial
|
seqread = seqreadserial
|
||||||
|
seqread.prepare(fullFilePathStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
seqread.prepare(fullFilePathStr)
|
|
||||||
|
|
||||||
con.clear()
|
con.clear()
|
||||||
con.curs_set(0)
|
con.curs_set(0)
|
||||||
graphics.setGraphicsMode(4) // 4096-color mode
|
graphics.setGraphicsMode(4) // 4096-color mode
|
||||||
|
|||||||
@@ -38,7 +38,10 @@ function hsdpaDisableSequentialIO() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function hsdpaRewind() {
|
function hsdpaRewind() {
|
||||||
|
// send rewind command to the tape drive
|
||||||
sys.poke(HSDPA_REG_SEQ_IO_OPCODE, HSDPA_OPCODE_REWIND)
|
sys.poke(HSDPA_REG_SEQ_IO_OPCODE, HSDPA_OPCODE_REWIND)
|
||||||
|
|
||||||
|
readCount = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
function hsdpaSkip(bytes) {
|
function hsdpaSkip(bytes) {
|
||||||
@@ -46,8 +49,10 @@ function hsdpaSkip(bytes) {
|
|||||||
sys.poke(HSDPA_REG_SEQ_IO_ARG1, bytes & 0xFF) // LSB
|
sys.poke(HSDPA_REG_SEQ_IO_ARG1, bytes & 0xFF) // LSB
|
||||||
sys.poke(HSDPA_REG_SEQ_IO_ARG1 - 1, (bytes >> 8) & 0xFF) // MSB
|
sys.poke(HSDPA_REG_SEQ_IO_ARG1 - 1, (bytes >> 8) & 0xFF) // MSB
|
||||||
sys.poke(HSDPA_REG_SEQ_IO_ARG1 - 2, (bytes >> 16) & 0xFF) // MSB2
|
sys.poke(HSDPA_REG_SEQ_IO_ARG1 - 2, (bytes >> 16) & 0xFF) // MSB2
|
||||||
// Execute skip operation
|
// Execute skip operation (tape drive should fast forward)
|
||||||
sys.poke(HSDPA_REG_SEQ_IO_OPCODE, HSDPA_OPCODE_SKIP)
|
sys.poke(HSDPA_REG_SEQ_IO_OPCODE, HSDPA_OPCODE_SKIP)
|
||||||
|
|
||||||
|
readCount += bytes
|
||||||
}
|
}
|
||||||
|
|
||||||
function hsdpaReadToMemory(bytes, vmMemoryPointer) {
|
function hsdpaReadToMemory(bytes, vmMemoryPointer) {
|
||||||
@@ -116,8 +121,7 @@ function prepare(fullPath) {
|
|||||||
|
|
||||||
// Reset position for actual reading
|
// Reset position for actual reading
|
||||||
hsdpaRewind()
|
hsdpaRewind()
|
||||||
readCount = 0
|
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -190,12 +194,19 @@ function readString(length) {
|
|||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
function skip(n) {
|
function skip(n0) {
|
||||||
if (n <= 0) return
|
if (n0 <= 0) return
|
||||||
|
if (n0 < 16777215) {
|
||||||
// For HSDPA, we can skip efficiently without reading
|
hsdpaSkip(n0)
|
||||||
hsdpaSkip(n)
|
return
|
||||||
readCount += n
|
}
|
||||||
|
let n = n0
|
||||||
|
while (n > 0) {
|
||||||
|
let skiplen = Math.min(n, 16777215)
|
||||||
|
serial.println(`skip ${skiplen}; remaining: ${n}`)
|
||||||
|
hsdpaSkip(skiplen)
|
||||||
|
n -= skiplen
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getReadCount() {
|
function getReadCount() {
|
||||||
@@ -226,12 +237,15 @@ function isReady() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function seek(position) {
|
function seek(position) {
|
||||||
// Seek to absolute position
|
let relPos = position - readCount
|
||||||
hsdpaRewind()
|
if (position == 0) {
|
||||||
if (position > 0) {
|
return
|
||||||
hsdpaSkip(position)
|
} else if (position > 0) {
|
||||||
|
skip(relPos)
|
||||||
|
} else {
|
||||||
|
hsdpaRewind()
|
||||||
|
skip(position)
|
||||||
}
|
}
|
||||||
readCount = position
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function rewind() { seek(0) }
|
function rewind() { seek(0) }
|
||||||
|
|||||||
@@ -1318,23 +1318,46 @@ class GraphicsJSR223Delegate(private val vm: VM) {
|
|||||||
|
|
||||||
val rgbAddrIncVec = if (rgbAddr >= 0) 1 else -1
|
val rgbAddrIncVec = if (rgbAddr >= 0) 1 else -1
|
||||||
|
|
||||||
val totalPixels = width * height
|
// Get native resolution
|
||||||
|
val nativeWidth = gpu.config.width
|
||||||
|
val nativeHeight = gpu.config.height
|
||||||
|
|
||||||
// Process in 8KB chunks to balance memory usage and performance
|
// Calculate centering offset
|
||||||
|
val offsetX = (nativeWidth - width) / 2
|
||||||
|
val offsetY = (nativeHeight - height) / 2
|
||||||
|
|
||||||
|
// Clear framebuffer with transparent pixels first
|
||||||
|
val transparentRG = 0.toByte() // r=0, g=0
|
||||||
|
val transparentBA = 0.toByte() // b=0, a=0 (transparent)
|
||||||
|
|
||||||
|
// Fill entire framebuffer with transparent pixels
|
||||||
|
val totalNativePixels = (nativeWidth * nativeHeight).toLong()
|
||||||
|
// UnsafeHelper.unsafe.setMemory(gpu.framebuffer.ptr, totalNativePixels, transparentRG)
|
||||||
|
// UnsafeHelper.unsafe.setMemory(gpu.framebuffer2!!.ptr, totalNativePixels, transparentBA)
|
||||||
|
|
||||||
|
// Process video pixels in 8KB chunks to balance memory usage and performance
|
||||||
|
val totalVideoPixels = width * height
|
||||||
val chunkSize = 8192
|
val chunkSize = 8192
|
||||||
val rgChunk = ByteArray(chunkSize)
|
val rgChunk = ByteArray(chunkSize)
|
||||||
val baChunk = ByteArray(chunkSize)
|
val baChunk = ByteArray(chunkSize)
|
||||||
|
val positionChunk = IntArray(chunkSize) // Store framebuffer positions
|
||||||
|
|
||||||
var pixelsProcessed = 0
|
var pixelsProcessed = 0
|
||||||
|
|
||||||
while (pixelsProcessed < totalPixels) {
|
while (pixelsProcessed < totalVideoPixels) {
|
||||||
val pixelsInChunk = kotlin.math.min(chunkSize, totalPixels - pixelsProcessed)
|
val pixelsInChunk = kotlin.math.min(chunkSize, totalVideoPixels - pixelsProcessed)
|
||||||
|
|
||||||
// Batch process chunk of pixels
|
// Batch process chunk of pixels
|
||||||
for (i in 0 until pixelsInChunk) {
|
for (i in 0 until pixelsInChunk) {
|
||||||
val pixelIndex = pixelsProcessed + i
|
val pixelIndex = pixelsProcessed + i
|
||||||
val y = pixelIndex / width
|
val videoY = pixelIndex / width
|
||||||
val x = pixelIndex % width
|
val videoX = pixelIndex % width
|
||||||
|
|
||||||
|
// Calculate position in native framebuffer (centered)
|
||||||
|
val nativeX = videoX + offsetX
|
||||||
|
val nativeY = videoY + offsetY
|
||||||
|
val nativePos = nativeY * nativeWidth + nativeX
|
||||||
|
positionChunk[i] = nativePos
|
||||||
|
|
||||||
val rgbOffset = (pixelIndex.toLong() * 3) * rgbAddrIncVec
|
val rgbOffset = (pixelIndex.toLong() * 3) * rgbAddrIncVec
|
||||||
|
|
||||||
@@ -1344,25 +1367,27 @@ class GraphicsJSR223Delegate(private val vm: VM) {
|
|||||||
val b = vm.peek(rgbAddr + rgbOffset + rgbAddrIncVec * 2)!!.toUint()
|
val b = vm.peek(rgbAddr + rgbOffset + rgbAddrIncVec * 2)!!.toUint()
|
||||||
|
|
||||||
// Apply Bayer dithering and convert to 4-bit
|
// Apply Bayer dithering and convert to 4-bit
|
||||||
val r4 = ditherValue(r, x, y, frameCounter)
|
val r4 = ditherValue(r, videoX, videoY, frameCounter)
|
||||||
val g4 = ditherValue(g, x, y, frameCounter)
|
val g4 = ditherValue(g, videoX, videoY, frameCounter)
|
||||||
val b4 = ditherValue(b, x, y, frameCounter)
|
val b4 = ditherValue(b, videoX, videoY, frameCounter)
|
||||||
|
|
||||||
// Pack and store in chunk buffers
|
// Pack and store in chunk buffers
|
||||||
rgChunk[i] = ((r4 shl 4) or g4).toByte()
|
rgChunk[i] = ((r4 shl 4) or g4).toByte()
|
||||||
baChunk[i] = ((b4 shl 4) or 15).toByte()
|
baChunk[i] = ((b4 shl 4) or 15).toByte()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Batch write entire chunk to framebuffer
|
// Write pixels to their calculated positions in framebuffer
|
||||||
val pixelOffset = (pixelsProcessed).toLong()
|
for (i in 0 until pixelsInChunk) {
|
||||||
|
val pos = positionChunk[i].toLong()
|
||||||
gpu.let {
|
// Bounds check to ensure we don't write outside framebuffer
|
||||||
UnsafeHelper.memcpyRaw(
|
if (pos in 0 until totalNativePixels) {
|
||||||
rgChunk, UnsafeHelper.getArrayOffset(rgChunk),
|
UnsafeHelper.memcpyRaw(
|
||||||
null, it.framebuffer.ptr + pixelOffset, pixelsInChunk.toLong())
|
rgChunk, UnsafeHelper.getArrayOffset(rgChunk) + i,
|
||||||
UnsafeHelper.memcpyRaw(
|
null, gpu.framebuffer.ptr + pos, 1L)
|
||||||
baChunk, UnsafeHelper.getArrayOffset(baChunk),
|
UnsafeHelper.memcpyRaw(
|
||||||
null, it.framebuffer2!!.ptr + pixelOffset, pixelsInChunk.toLong())
|
baChunk, UnsafeHelper.getArrayOffset(baChunk) + i,
|
||||||
|
null, gpu.framebuffer2!!.ptr + pos, 1L)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pixelsProcessed += pixelsInChunk
|
pixelsProcessed += pixelsInChunk
|
||||||
|
|||||||
@@ -1527,7 +1527,7 @@ static int write_tev_header(FILE *output, tev_encoder_t *enc) {
|
|||||||
// Video parameters
|
// Video parameters
|
||||||
uint16_t width = enc->width;
|
uint16_t width = enc->width;
|
||||||
uint16_t height = enc->progressive_mode ? enc->height : enc->height * 2;
|
uint16_t height = enc->progressive_mode ? enc->height : enc->height * 2;
|
||||||
uint8_t fps = enc->fps;
|
uint8_t fps = enc->output_fps;
|
||||||
uint32_t total_frames = enc->total_frames;
|
uint32_t total_frames = enc->total_frames;
|
||||||
uint8_t qualityY = enc->qualityY;
|
uint8_t qualityY = enc->qualityY;
|
||||||
uint8_t qualityCo = enc->qualityCo;
|
uint8_t qualityCo = enc->qualityCo;
|
||||||
@@ -1780,13 +1780,19 @@ static int get_video_metadata(tev_encoder_t *config) {
|
|||||||
config->total_frames = (int)(config->duration * config->fps);
|
config->total_frames = (int)(config->duration * config->fps);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// calculate new total_frames if user has requested custom framerate
|
||||||
|
float inputFramerate;
|
||||||
|
if (config->is_ntsc_framerate) {
|
||||||
|
inputFramerate = config->fps * 1000.f / 1001.f;
|
||||||
|
} else {
|
||||||
|
inputFramerate = config->fps * 1.f;
|
||||||
|
}
|
||||||
|
|
||||||
|
config->total_frames = (int)(config->total_frames * (config->output_fps / inputFramerate));
|
||||||
|
|
||||||
fprintf(stderr, "Video metadata:\n");
|
fprintf(stderr, "Video metadata:\n");
|
||||||
fprintf(stderr, " Frames: %d\n", config->total_frames);
|
fprintf(stderr, " Frames: %d\n", config->total_frames);
|
||||||
if (config->is_ntsc_framerate) {
|
fprintf(stderr, " FPS: %.2f\n", inputFramerate);
|
||||||
fprintf(stderr, " FPS: %.2f\n", config->fps * 1000.f / 1001.f);
|
|
||||||
} else {
|
|
||||||
fprintf(stderr, " FPS: %d\n", config->fps);
|
|
||||||
}
|
|
||||||
fprintf(stderr, " Duration: %.2fs\n", config->duration);
|
fprintf(stderr, " Duration: %.2fs\n", config->duration);
|
||||||
fprintf(stderr, " Audio: %s\n", config->has_audio ? "Yes" : "No");
|
fprintf(stderr, " Audio: %s\n", config->has_audio ? "Yes" : "No");
|
||||||
fprintf(stderr, " Resolution: %dx%d (%s)\n", config->width, config->height,
|
fprintf(stderr, " Resolution: %dx%d (%s)\n", config->width, config->height,
|
||||||
@@ -1923,7 +1929,7 @@ static int process_audio(tev_encoder_t *enc, int frame_num, FILE *output) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Calculate how much audio time each frame represents (in seconds)
|
// Calculate how much audio time each frame represents (in seconds)
|
||||||
double frame_audio_time = 1.0 / enc->fps;
|
double frame_audio_time = 1.0 / enc->output_fps;
|
||||||
|
|
||||||
// Calculate how much audio time each MP2 packet represents
|
// Calculate how much audio time each MP2 packet represents
|
||||||
// MP2 frame contains 1152 samples at 32kHz = 0.036 seconds
|
// MP2 frame contains 1152 samples at 32kHz = 0.036 seconds
|
||||||
@@ -2198,6 +2204,7 @@ int main(int argc, char *argv[]) {
|
|||||||
if (test_mode) {
|
if (test_mode) {
|
||||||
// Test mode: generate solid colour frames
|
// Test mode: generate solid colour frames
|
||||||
enc->fps = 1;
|
enc->fps = 1;
|
||||||
|
enc->output_fps = 1;
|
||||||
enc->total_frames = 15;
|
enc->total_frames = 15;
|
||||||
enc->has_audio = 0;
|
enc->has_audio = 0;
|
||||||
printf("Test mode: Generating 15 solid colour frames\n");
|
printf("Test mode: Generating 15 solid colour frames\n");
|
||||||
@@ -2217,7 +2224,7 @@ int main(int argc, char *argv[]) {
|
|||||||
int format = detect_subtitle_format(enc->subtitle_file);
|
int format = detect_subtitle_format(enc->subtitle_file);
|
||||||
const char *format_name = (format == 1) ? "SAMI" : "SubRip";
|
const char *format_name = (format == 1) ? "SAMI" : "SubRip";
|
||||||
|
|
||||||
enc->subtitle_list = parse_subtitle_file(enc->subtitle_file, enc->fps);
|
enc->subtitle_list = parse_subtitle_file(enc->subtitle_file, enc->output_fps);
|
||||||
if (enc->subtitle_list) {
|
if (enc->subtitle_list) {
|
||||||
enc->has_subtitles = 1;
|
enc->has_subtitles = 1;
|
||||||
enc->current_subtitle = enc->subtitle_list;
|
enc->current_subtitle = enc->subtitle_list;
|
||||||
@@ -2398,7 +2405,7 @@ int main(int argc, char *argv[]) {
|
|||||||
printf("\nEncoding complete!\n");
|
printf("\nEncoding complete!\n");
|
||||||
printf(" Frames encoded: %d\n", frame_count);
|
printf(" Frames encoded: %d\n", frame_count);
|
||||||
printf(" - sync packets: %d\n", sync_packet_count);
|
printf(" - sync packets: %d\n", sync_packet_count);
|
||||||
printf(" Framerate: %d\n", enc->fps);
|
printf(" Framerate: %d\n", enc->output_fps);
|
||||||
printf(" Output size: %zu bytes\n", enc->total_output_bytes);
|
printf(" Output size: %zu bytes\n", enc->total_output_bytes);
|
||||||
|
|
||||||
// Calculate achieved bitrate
|
// Calculate achieved bitrate
|
||||||
|
|||||||
Reference in New Issue
Block a user