mirror of
https://git.k-space.ee/arti/vt1200.git
synced 2026-11-01 12:14:56 +02:00
feat(badapple): host-tested movie decoder
This commit is contained in:
1 parent
5a1f8429f0
commit
beb45942ad
6 files changed
+329
No files matched your search
@@ -0,0 +1,157 @@
|
||||
/* movie.c -- pure decoder for the BAPL tile-delta movie format.
|
||||
*
|
||||
* C89-compatible: the 1987 TMS34010 compiler has no stdint.h and no mixed
|
||||
* declarations. The host test compiles this same file with gcc -std=c99.
|
||||
*/
|
||||
#include "movie.h"
|
||||
|
||||
#define HEADER_SIZE 36
|
||||
#define INDEX_ENTRY_SIZE 6
|
||||
|
||||
static unsigned int rd16(const unsigned char *p)
|
||||
{
|
||||
return (unsigned int)p[0] | ((unsigned int)p[1] << 8);
|
||||
}
|
||||
|
||||
static unsigned long rd32(const unsigned char *p)
|
||||
{
|
||||
return (unsigned long)p[0] | ((unsigned long)p[1] << 8)
|
||||
| ((unsigned long)p[2] << 16) | ((unsigned long)p[3] << 24);
|
||||
}
|
||||
|
||||
int movie_open(const unsigned char *data, unsigned long len,
|
||||
struct movie_header *header)
|
||||
{
|
||||
unsigned int version;
|
||||
|
||||
if (data == 0 || header == 0 || len < HEADER_SIZE) {
|
||||
return -1;
|
||||
}
|
||||
if (data[0] != 'B' || data[1] != 'A' || data[2] != 'P' || data[3] != 'L') {
|
||||
return -1;
|
||||
}
|
||||
version = rd16(data + 4);
|
||||
if (version != 1) {
|
||||
return -1;
|
||||
}
|
||||
header->width = rd16(data + 6);
|
||||
header->height = rd16(data + 8);
|
||||
header->scale = rd16(data + 10);
|
||||
header->tile = rd16(data + 12);
|
||||
header->tiles_x = rd16(data + 14);
|
||||
header->tiles_y = rd16(data + 16);
|
||||
header->frame_count = (unsigned int)rd32(data + 18);
|
||||
header->index_offset = (unsigned int)rd32(data + 22);
|
||||
header->data_offset = (unsigned int)rd32(data + 26);
|
||||
header->source_fps = rd16(data + 30);
|
||||
header->display_hz = rd16(data + 32);
|
||||
if (header->scale == 0 || header->tile == 0
|
||||
|| header->tile > 32 || header->tiles_x == 0
|
||||
|| header->tiles_y == 0) {
|
||||
return -1;
|
||||
}
|
||||
if ((unsigned long)header->index_offset
|
||||
+ (unsigned long)header->frame_count * INDEX_ENTRY_SIZE > len) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int movie_decode_frame(const unsigned char *data, unsigned long len,
|
||||
unsigned int frame,
|
||||
const struct movie_header *header,
|
||||
movie_fill_fn fill, void *ctx)
|
||||
{
|
||||
unsigned long index_off;
|
||||
unsigned long off;
|
||||
unsigned int count;
|
||||
unsigned int i;
|
||||
unsigned int j;
|
||||
unsigned int tile_index;
|
||||
unsigned int packed;
|
||||
unsigned int bg;
|
||||
unsigned int rects;
|
||||
unsigned int tx;
|
||||
unsigned int ty;
|
||||
unsigned int value;
|
||||
unsigned int x0;
|
||||
unsigned int y0;
|
||||
unsigned int w;
|
||||
unsigned int h;
|
||||
int sx;
|
||||
int sy;
|
||||
|
||||
if (data == 0 || header == 0 || fill == 0) {
|
||||
return -1;
|
||||
}
|
||||
if (frame >= header->frame_count) {
|
||||
return -1;
|
||||
}
|
||||
index_off = (unsigned long)header->index_offset
|
||||
+ (unsigned long)frame * INDEX_ENTRY_SIZE;
|
||||
if (index_off + INDEX_ENTRY_SIZE > len) {
|
||||
return -1;
|
||||
}
|
||||
off = rd32(data + index_off);
|
||||
if (off + 2 > len) {
|
||||
return -1;
|
||||
}
|
||||
count = rd16(data + off);
|
||||
off += 2;
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
if (off + 4 > len) {
|
||||
return -1;
|
||||
}
|
||||
tile_index = rd16(data + off);
|
||||
packed = rd16(data + off + 2);
|
||||
off += 4;
|
||||
if (tile_index >= header->tiles_x * header->tiles_y) {
|
||||
return -1;
|
||||
}
|
||||
bg = packed >> 15;
|
||||
rects = packed & 0x7FFF;
|
||||
if (off + (unsigned long)rects * 3 > len) {
|
||||
return -1;
|
||||
}
|
||||
tx = tile_index % header->tiles_x;
|
||||
ty = tile_index / header->tiles_x;
|
||||
|
||||
/* The whole tile first: the old pixels outside every new rect must
|
||||
* go away. The C test skips this first call per tile. */
|
||||
sx = (int)(tx * header->tile * header->scale);
|
||||
sy = (int)(ty * header->tile * header->scale);
|
||||
fill(sx, sy, (int)(header->tile * header->scale),
|
||||
(int)(header->tile * header->scale), (int)bg, ctx);
|
||||
|
||||
for (j = 0; j < rects; j++) {
|
||||
value = (unsigned int)data[off]
|
||||
| ((unsigned int)data[off + 1] << 8)
|
||||
| ((unsigned int)data[off + 2] << 16);
|
||||
off += 3;
|
||||
x0 = value & 0x1F;
|
||||
y0 = (value >> 5) & 0x1F;
|
||||
w = ((value >> 10) & 0x1F) + 1;
|
||||
h = ((value >> 15) & 0x1F) + 1;
|
||||
sx = (int)((tx * header->tile + x0) * header->scale);
|
||||
sy = (int)((ty * header->tile + y0) * header->scale);
|
||||
fill(sx, sy, (int)(w * header->scale),
|
||||
(int)(h * header->scale), (int)(1 - bg), ctx);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
unsigned int movie_checksum(const unsigned char *data, unsigned long len)
|
||||
{
|
||||
unsigned long sum = 0;
|
||||
unsigned long i;
|
||||
|
||||
if (data == 0) {
|
||||
return 0;
|
||||
}
|
||||
for (i = 0; i < len; i++) {
|
||||
sum = (sum + data[i]) & 0xFFFFFFFFUL;
|
||||
}
|
||||
return (unsigned int)sum;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
#ifndef BADAPPLE_MOVIE_H
|
||||
#define BADAPPLE_MOVIE_H
|
||||
|
||||
struct movie_header {
|
||||
unsigned int width;
|
||||
unsigned int height;
|
||||
unsigned int scale;
|
||||
unsigned int tile;
|
||||
unsigned int tiles_x;
|
||||
unsigned int tiles_y;
|
||||
unsigned int frame_count;
|
||||
unsigned int index_offset;
|
||||
unsigned int data_offset;
|
||||
unsigned int source_fps;
|
||||
unsigned int display_hz;
|
||||
};
|
||||
|
||||
typedef void (*movie_fill_fn)(int x, int y, int w, int h, int color,
|
||||
void *ctx);
|
||||
|
||||
int movie_open(const unsigned char *data, unsigned long len,
|
||||
struct movie_header *header);
|
||||
|
||||
int movie_decode_frame(const unsigned char *data, unsigned long len,
|
||||
unsigned int frame,
|
||||
const struct movie_header *header,
|
||||
movie_fill_fn fill, void *ctx);
|
||||
|
||||
unsigned int movie_checksum(const unsigned char *data, unsigned long len);
|
||||
|
||||
#endif
|
||||
Binary file not shown.
@@ -0,0 +1,85 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "../movie.h"
|
||||
|
||||
struct ctx {
|
||||
int frame;
|
||||
int last_tile;
|
||||
int have_last;
|
||||
};
|
||||
|
||||
static void report_fill(int x, int y, int w, int h, int color, void *opaque)
|
||||
{
|
||||
struct ctx *ctx = (struct ctx *)opaque;
|
||||
int lx = x / 2;
|
||||
int ly = y / 2;
|
||||
int lw = w / 2;
|
||||
int lh = h / 2;
|
||||
int tx = lx / 32;
|
||||
int ty = ly / 32;
|
||||
int tile = ty * 2 + tx;
|
||||
|
||||
/* The decoder's first call for a tile is its background. */
|
||||
if (!ctx->have_last || tile != ctx->last_tile) {
|
||||
ctx->last_tile = tile;
|
||||
ctx->have_last = 1;
|
||||
return;
|
||||
}
|
||||
printf("%d %d %d %d %d %d %d\n", ctx->frame, tile, 1 - color,
|
||||
lx % 32, ly % 32, lw, lh);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
FILE *file;
|
||||
long size;
|
||||
unsigned char *data;
|
||||
struct movie_header header;
|
||||
unsigned int frame;
|
||||
struct ctx ctx;
|
||||
|
||||
if (argc != 2) {
|
||||
fprintf(stderr, "usage: %s movie.bin\n", argv[0]);
|
||||
return 2;
|
||||
}
|
||||
file = fopen(argv[1], "rb");
|
||||
if (file == 0) {
|
||||
fprintf(stderr, "cannot open %s\n", argv[1]);
|
||||
return 2;
|
||||
}
|
||||
fseek(file, 0, SEEK_END);
|
||||
size = ftell(file);
|
||||
fseek(file, 0, SEEK_SET);
|
||||
data = (unsigned char *)malloc((size_t)size);
|
||||
if (data == 0) {
|
||||
fclose(file);
|
||||
return 2;
|
||||
}
|
||||
if (fread(data, 1, (size_t)size, file) != (size_t)size) {
|
||||
fclose(file);
|
||||
free(data);
|
||||
return 2;
|
||||
}
|
||||
fclose(file);
|
||||
|
||||
if (movie_open(data, (unsigned long)size, &header) != 0) {
|
||||
fprintf(stderr, "movie_open failed\n");
|
||||
free(data);
|
||||
return 1;
|
||||
}
|
||||
printf("frames=%u\n", header.frame_count);
|
||||
for (frame = 0; frame < header.frame_count; frame++) {
|
||||
ctx.frame = (int)frame;
|
||||
ctx.last_tile = -1;
|
||||
ctx.have_last = 0;
|
||||
if (movie_decode_frame(data, (unsigned long)size, frame, &header,
|
||||
report_fill, &ctx) != 0) {
|
||||
fprintf(stderr, "decode failed at frame %u\n", frame);
|
||||
free(data);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
free(data);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Write the two-frame fixture the C decoder test reads."""
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
if str(ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
from pipeline.movie import (
|
||||
FrameRecord,
|
||||
MovieHeader,
|
||||
Rect,
|
||||
TileRecord,
|
||||
write_movie,
|
||||
)
|
||||
|
||||
OUT = ROOT / "player" / "tests" / "fixture.bin"
|
||||
|
||||
|
||||
def main() -> int:
|
||||
frames = [
|
||||
FrameRecord(1, 0, [TileRecord(0, 0, [Rect(0, 0, 32, 32)])]),
|
||||
FrameRecord(1, 0, [TileRecord(1, 1, [Rect(1, 1, 2, 3)])]),
|
||||
]
|
||||
header = MovieHeader(64, 32, 2, 32, 2, 1, len(frames), 60, 72)
|
||||
OUT.parent.mkdir(parents=True, exist_ok=True)
|
||||
write_movie(str(OUT), frames, header)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,23 @@
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
def test_c_decoder_matches_python(tmp_path):
|
||||
subprocess.run([sys.executable, str(ROOT / "tests" / "make_fixture.py")],
|
||||
check=True)
|
||||
exe = tmp_path / "decode"
|
||||
subprocess.run([
|
||||
"gcc", "-std=c99", "-Wall", "-Werror",
|
||||
str(ROOT / "player" / "movie.c"),
|
||||
str(ROOT / "player" / "tests" / "test_movie_decode.c"),
|
||||
"-o", str(exe),
|
||||
], check=True)
|
||||
out = subprocess.run(
|
||||
[str(exe), str(ROOT / "player" / "tests" / "fixture.bin")],
|
||||
check=True, capture_output=True, text=True).stdout
|
||||
assert "frames=2" in out
|
||||
assert "0 0 0 0 0 32 32" in out
|
||||
assert "1 1 1 1 1 2 3" in out
|
||||
Reference in new issue
Block a user