popcorn: add converter

pull/13/head
graham sanderson 2021-01-14 20:52:46 -06:00
rodzic 2505c87284
commit c1390f40e7
6 zmienionych plików z 895 dodań i 1 usunięć

Wyświetl plik

@ -10,6 +10,12 @@ RP2040 design was final, so they were all developed with that fixed 48MHz system
that you wouldn't necessarily need to if you had more clock speed available (which you do), but on the plus side,
you have that much more time to do even more things!
Finally, you may wonder why many of these demos set the system clock to 48Mhz. The reason is that until we had physical
chips, we were running at a fixed 48Mhz system clock using an FPGA. Most of these examples were written before the
RP2040 design was final, so were all developed with that fixed 48MHz system clock. As a result some of the examples do things in a way
that you wouldn't necessarily need to if you had more clock speed available (which you do), but on the plus side,
you have that much more time to do even more things!
## Full Applications
Name|Description

Wyświetl plik

@ -37,4 +37,6 @@ if (PICO_ON_DEVICE)
pico_audio_i2s)
pico_add_extra_outputs(popcorn)
endif()
else()
add_subdirectory(converter)
endif()

Wyświetl plik

@ -33,4 +33,4 @@ Pause and then unpause to reset playback speed to 1x
### Converting
TODO - will upload a converter and instructions soon.
see [here](converter/README.md)

Wyświetl plik

@ -0,0 +1,9 @@
cmake_minimum_required(VERSION 3.9)
project(converter)
set(CMAKE_CXX_STANDARD 17)
include_directories(../libs/render src)
add_executable(converter
src/convert.cpp
)

Wyświetl plik

@ -0,0 +1,47 @@
# Building
Note these instructions apply to Unix; they have not been tested on other platforms
To build the converter, either do a `PICO_PLATFORM=host` build of pico-playground (which will include it),
or from this directory just do
```bash
mkidr build
cd build
cmake -DCMAKE_BUILD_TYPE=Release ..
make
```
Converter takes two inputs (and you're going to need to read the `ffmpeg` docs!)
# .rgb file
This must be 320x240 30fps 24 bit raw RGB
e.g.
```
ffmpeg -i input.mkv -vf "crop=1088:816:416:132, scale=(iw*sar)*max(320/(iw*sar)\,240/ih):ih*max(320/(iw*sar)\,240/ih)" -c:v rawvideo -pix_fmt rgb24 -r 30 movie.rgb
```
`-c:v rawvideo -pix_fmt rgb24 -r 30 movie.rgb` are key here (and the 320x240 output size)
# .pcm file
This must be stereo 44100Hz signed 16 bit little-endian raw data
e.g.
```
ffmpeg -i input.mkv -ac 2 -f s16le -c:a pcm_s16le -ar 44100 movie.pcm
```
# Converting
You can then do:
```c
converter movie.rgb movie.pcm movie.pl2
```
If the inputs are not as specified, then the converter will likely crash!

Wyświetl plik

@ -0,0 +1,830 @@
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <cstdint>
#include <cassert>
#include <algorithm>
#include <map>
#include <set>
#include <functional>
#include <cstdio>
#include <cstring>
// add a word at the end of every row with debug information
// #define ADD_EOR_DEBUGGING
#define ENCODE_565
#define RSHIFT 0
#ifdef ENCODE_565
#define GSHIFT 6
#define BSHIFT 11
#else
#define GSHIFT 5
#define BSHIFT 10
#endif
inline static uint16_t to_5n5(uint32_t t) {
#ifndef ENCODE_565
return ((t & 0xf8) >> 3) | ((t & 0xf800) >> 6 | ((t & 0xf80000) >> 9));
#else
return ((t & 0xf8) >> 3) | ((t & 0xf800) >> 5 | ((t & 0xf80000) >> 8));
#endif
}
// uint32_t as may be used for 2 pixels
inline static uint32_t to_5n5(uint32_t r5, uint32_t g5, uint32_t b5) {
#ifndef ENCODE_565
return (b5 << 10) | (g5 << 5) | (r5);
#else
return (b5 << 11) | (g5 << 6) | (r5);
#endif
}
uint32_t sorted_keys[32] = {
0x0000,
0x0020,
0x0421,
0x0420,
0x8420,
0x0400,
0x0021,
0x0441,
0x0041,
0x0821,
0x00442,
0x00401,
0x08020,
0x08440,
0x08400,
0x08820,
0x00822,
0x08040,
0x10820,
0x08800,
0x00040,
0x08041,
0x00422,
0x00801,
0x00842,
0x00042,
0x10440,
0x00462,
0x00062,
0x00463,
0x00440,
0x08021,
};
static uint8_t *key_lookup;
static uint16_t *key_dist;
static int8_t *key_offset;
std::vector<unsigned char> converted;
uint8_t dp[] = {
0, 8, 2, 10,
12, 4, 14, 6,
3, 11, 1, 9,
15, 7, 13, 5
};
static inline void dither(uint8_t& c, uint x3, uint y3) {
uint x = c + dp[x3 + y3 * 4] / 2;
if (x > 255) x = 255;
c = (x & 0xf8u);
}
static inline void dither(uint8_t& r, uint8_t& g, uint8_t &b, uint x3, uint y3) {
dither(r, x3, y3);
dither(g, x3, y3);
dither(b, x3, y3);
}
void dither_image(uint w, uint h, std::vector<unsigned char> &source)
{
for(uint y=0; y < h; y++) {
uint8_t *base = &source[y * w * 3];
for(uint x = 0; x < w; x+= 4) {
#if 1
dither(base[0], base[1], base[2], 0, y&3u);
dither(base[3], base[4], base[5], 1, y&3u);
dither(base[6], base[7], base[8], 2, y&3u);
dither(base[9], base[10], base[11], 3, y&3u);
#endif
base += 12;
}
}
}
static int min_cost = 0x7fffffff;
static int max_cost = 0;
static int worst_frame = 0;
static long total_cost = 0;
static long total_vals = 0;
static uint rgb16(uint8_t r, uint8_t g, uint8_t b) {
return ((b>>3) << 11) | ((g>>3)<<6) | (r>>3);
}
static uint rgb15(uint8_t r, uint8_t g, uint8_t b) {
return ((b>>3) << 10) | ((g>>3)<<5) | (r>>3);
}
static inline void write_word(uint8_t *&dest, uint v) {
*dest++ = v & 0xff;
*dest++ = v >> 8;
}
static inline void write_word_be(uint8_t *&dest, uint v) {
*dest++ = v >> 8;
*dest++ = v & 0xff;
}
static uint32_t *row0_222;
static uint32_t *row1_222;
static uint32_t *row0_5;
static uint32_t *row1_5;
static inline uint get_key(uint min, uint da, uint db, uint dc, uint dd) {
assert(da >= min);
assert(db >= min);
assert(dc >= min);
assert(dd >= min);
return (((da - min) >> 3u) << 5u * 3u) |
(((db - min) >> 3u) << 5u * 2u) |
(((dc - min) >> 3u) << 5u * 1u) |
(((dd - min) >> 3u) << 5u * 0u);
}
#ifndef ENCODE_565
bool compress_image(const char *name, uint w, uint h, std::vector<unsigned char> &source, std::vector<unsigned char> &dest, std::vector<uint32_t> &line_offsets, uint max_rdist, uint max_gdist, uint max_bdist, uint extra_line_words = 0)
{
bool use_56bit_raw = true;
assert(!((w|h)&1u));
if (!key_lookup)
{
key_lookup = (uint8_t *)calloc(1,0x100000);
key_dist = (uint16_t *) calloc(2, 0x100000);
key_offset = (int8_t *) calloc(1, 0x100000);
for(int i = 0; i < 0x100000; i++)
{
key_dist[i] = 0x7fffu;
}
for(int i = 0; i < 32; i++)
{
uint8_t choice = (i < 4) ? 0x81 + i : 1 + i;
key_lookup[sorted_keys[i]] = choice;
key_dist[sorted_keys[i]] = 0;
for(int o = -3; o < 3; o++) { // todo really?
// for(int o=0;o<1;o++) {
uint da = (sorted_keys[i] >> (5u * 3u)) & 0x1f;
uint db = (sorted_keys[i] >> (5u * 2u)) & 0x1f;
uint dc = (sorted_keys[i] >> (5u * 1u)) & 0x1f;
uint dd = (sorted_keys[i] >> (5u * 0u)) & 0x1f;
// if (da == db && db == dc & dc == dd && (o == da || o == -da)) continue;
for(uint a = 0; a < 32; a++)
{
for(uint b = 0; b < 32; b++)
{
for(uint c = 0; c < 32; c++)
{
for(uint d = 0; d < 32; d++)
{
uint j = (a << (5u * 3u)) | (b << (5u * 2u)) | (c << (5u * 1u) | (d << (5u * 0u)));
uint score = (a + o - da) * (a + o - da);
score += (b + o - db) * (b + o - db);
score += (c + o - dc) * (c + o - dc);
score += (d + o - dd) * (d + o - dd);
if (score < key_dist[j] || (score == key_dist[j] && !o))
{
if (score == 0) {
// printf("pants\n");
}
key_dist[j] = score;
key_lookup[j] = choice;
key_offset[j] = o;
}
}
}
}
}
}
}
for(int i = 0; i < 32; i++)
{
assert(key_dist[sorted_keys[i]] == 0);
}
uint t = 0;
for(int i = 0; i < 0x100000; i++) {
if (!key_dist[i]) {
// printf("%d %d %d\n", i, key_offset[i], key_dist[i]);
t++;
}
if (key_offset[i]) {
//printf("%d %d %d\n", i, key_offset[i], key_dist[i]);
}
}
// assert(t == 32);
assert(t == 128); // todo why
}
uint32_t counts[4] = {0,0,0,0};
uint8_t *d = &dest[0];
line_offsets.clear();
for(uint y=0; y < h; y+=2) {
uint8_t *base = &source[y * w * 3];
uint8_t *base2 = base + w * 3;
line_offsets.push_back(d - &dest[0]);
for(uint x = 0; x < w; x+= 2) {
uint rmin = std::min({base[0], base[3], base2[0], base2[3]});
uint gmin = std::min({base[1], base[4], base2[1], base2[4]});
uint bmin = std::min({base[2], base[5], base2[2], base2[5]});
uint rkey = get_key(rmin, base[0], base[3], base2[0], base2[3]);
uint16_t rdist = key_dist[rkey];
uint8_t rval = key_lookup[rkey];
uint gkey = get_key(gmin, base[1], base[4], base2[1], base2[4]);
uint16_t gdist = key_dist[gkey];
uint8_t gval = key_lookup[gkey];
uint bkey = get_key(bmin, base[2], base[5], base2[2], base2[5]);
uint16_t bdist = key_dist[bkey];
uint8_t bval = key_lookup[bkey];
uint16_t dist = MAX(rdist, MAX(gdist, bdist));
uint16_t val = MIN(rval, MIN(gval, bval));
// if (dist) {
// if (rdist > 4 || gdist > 4 || bdist > 4) {
if (rdist > max_rdist || gdist > max_gdist || bdist > max_bdist) {
// if (rdist <= 4 && gdist <= 4 && bdist <= 4) {
// memset(base, 0, 6);
// if (key_offset[rkey]) base[0] |= 0xf8;
// if (key_offset[gkey]) base[1] |= 0xf8;
// if (key_offset[bkey]) base[2] |= 0xf8;
// //printf("oobla\n");
// }
if (use_56bit_raw)
{
// #2 : encode ABCD raw as 555, 454, 454, 454 in 7 bytes
// v v v v v v
// | Ga2 Ga1 Ga0 Ra4 : Ra3 Ra2 Ra1 Ra0
// | "1" Ba4 Ba3 Ba2 : Ba1 Ba0 Ga4 Ga3
//
// | Gb2 Gb1 Gb0 Rb4 : Rb3 Rb2 Rb1 Bd2
// | "0" Bb4 Bb3 Bb2 : Bb1 Gd4 Gb4 Gb3
//
// | Gc2 Gc1 Gc0 Rc4 : Rc3 Rc2 Rc1 Bd1
// | Bd3 Bc4 Bc3 Bc2 : Bc1 Gd3 Gc4 Gc3
//
// | Gd2 Gd1 Gd0 Rd4 : Rd3 Rd2 Rd1 Bd4 |
uint32_t lo = 0x8000u | rgb15(base[0], base[1], base[2]) | (rgb15(base[3], base[4], base[5]) << 16);
uint32_t hi = rgb15(base2[0], base2[1], base2[2]) | (rgb15(base2[3], base2[4], base2[5]) << 16);
// from "0" Bd4 Bd3 Bd2 : Bd1 Bd0 Gd4 Gd3
*d++ = lo;
*d++ = lo >> 8u;
*d++ = ((lo >> 16u) & ~1u) | ((hi & 0x10000000u)?1u:0);
*d++ = ((lo >> 24u) & ~4u) | ((hi & 0x02000000u)?4u:0);
*d++ = (hi & ~1u) | ((hi & 0x08000000u)?1u:0);
*d++ = ((hi >> 8u) & ~0x84u) | ((hi & 0x20000000u)?0x80u:0) | ((hi & 0x01000000u)?0x4u:0);
*d++ = ((hi >> 16u) & ~1u) | ((hi & 0x40000000u)?1u:0);
counts[3]++;
} else
{
// #1 : encode ABCD raw as 555, 555, 555, 555 in 8 bytes
//
// | Ga2 Ga1 Ga0 Ra4 : Ra3 Ra2 Ra1 Ra0
// | "1" Ba4 Ba3 Ba2 : Ba1 Ba0 Ga4 Ga3
// | Gb2 Gb1 Gb0 Rb4 : Rb3 Rb2 Rb1 Rb0
// | "1" Bb4 Bb3 Bb2 : Bb1 Bb0 Gb4 Gb3
// | Gc2 Gc1 Gc0 Rc4 : Rc3 Rc2 Rc1 Rc0
// | "0" Bc4 Bc3 Bc2 : Bc1 Bc0 Gc4 Gc3
// | Gd2 Gd1 Gd0 Rd4 : Rd3 Rd2 Rd1 Rc0
// | "0" Bd4 Bd3 Bd2 : Bd1 Bd0 Gd4 Gd3 |
//
write_word(d, 0x8000u | rgb15(base[0], base[1], base[2]));
write_word(d, 0x8000u | rgb15(base[3], base[4], base[5]));
write_word(d, rgb15(base2[0], base2[1], base2[2]));
write_word(d, rgb15(base2[3], base2[4], base2[5]));
counts[0]++;
}
} else if (val < 0x80) {
// #1 encode 555 color, then 1 of 32 2x2 add patterns for each component in 4 bytes total
assert(rval && gval && bval);
// | Ga2 Ga1 Ga0 Ra4 : Ra3 Ra2 Ra1 Ra0
// | "0" Ba4 Ba3 Ba2 : Ba1 Ba0 Ga4 Ga3
// |
// | "1" Bp4 Bp3 Bp2 : Bp1 Bp0 Gp4 Gp3
// | Gp2 Gp1 Gp0 Rp4 : Rp3 Rp2 Rp1 Rp0
write_word(d, rgb15(rmin-key_offset[rkey], gmin - key_offset[gkey], bmin - key_offset[bkey]));
write_word_be(d, 0x8000u | rgb15((rval - 1u)<<3u, (gval -1u)<<3u, (bval-1u)<<3u));
counts[1]++;
} else {
// #1 encode 555 color, then 1 of 4 2x2 add patterns for each component in 3 bytes total
// | Ga2 Ga1 Ga0 Ra4 : Ra3 Ra2 Ra1 Ra0
// | "0" Ba4 Ba3 Ba2 : Ba1 Ba0 Ga4 Ga3
// |
// | "0" "0" Bp1 Bp0 : Gp1 Gp0 Rp1 Rp0
write_word(d, rgb15(rmin-key_offset[rkey], gmin - key_offset[gkey], bmin - key_offset[bkey]));
*d++ = ((bval - 0x81u) << 4u) | ((gval - 0x81u) << 2u) | (rval - 0x81u);
counts[2]++;
}
base += 6;
base2 += 6;
}
while (3u & (uintptr_t)d) *d++ = 0; // need aligned rows
for(int i=0;i<extra_line_words;i++) {
*d++ = 0;
*d++ = 0;
*d++ = 0;
*d++ = 0;
}
}
line_offsets.push_back(d - &dest[0]);
while (d < dest.end().base()) *d++ = 0;
//return counts[0]*8 + counts[3] * 7 + counts[1] *3 + counts[2] * 4;
int cost = counts[0]*8 + counts[3] * 7 + counts[2] *3 + counts[1] * 4;
min_cost = MIN(cost, min_cost);
bool rc = cost > max_cost;
max_cost = MAX(cost, max_cost);
total_cost += cost;
total_vals += w*h;
// printf("%s %d*8 %d*7 %d*4 %d*3\n", name, counts[0], counts[3], counts[1], counts[2]);
return rc;
}
#else
bool compress_image(const char *name, uint w, uint h, std::vector<unsigned char> &source, std::vector<unsigned char> &dest, std::vector<uint32_t> &line_offsets, uint max_rdist, uint max_gdist, uint max_bdist, uint extra_line_words = 0)
{
bool use_56bit_raw = true;
assert(!((w|h)&1u));
if (!key_lookup)
{
key_lookup = (uint8_t *) calloc(1, 0x100000);
key_dist = (uint16_t *) calloc(2, 0x100000);
key_offset = (int8_t *) calloc(1, 0x100000);
for(int i = 0; i < 0x100000; i++)
{
key_dist[i] = 0x7fffu;
}
for(int i = 0; i < 32; i++)
{
uint8_t choice = (i < 4) ? 0x81 + i : 1 + i;
key_lookup[sorted_keys[i]] = choice;
key_dist[sorted_keys[i]] = 0;
for(int o = -3; o < 3; o++) { // todo really?
// for(int o=0;o<1;o++) {
uint da = (sorted_keys[i] >> (5u * 3u)) & 0x1f;
uint db = (sorted_keys[i] >> (5u * 2u)) & 0x1f;
uint dc = (sorted_keys[i] >> (5u * 1u)) & 0x1f;
uint dd = (sorted_keys[i] >> (5u * 0u)) & 0x1f;
// if (da == db && db == dc & dc == dd && (o == da || o == -da)) continue;
for(uint a = 0; a < 32; a++)
{
for(uint b = 0; b < 32; b++)
{
for(uint c = 0; c < 32; c++)
{
for(uint d = 0; d < 32; d++)
{
uint j = (a << (5u * 3u)) | (b << (5u * 2u)) | (c << (5u * 1u) | (d << (5u * 0u)));
uint score = (a + o - da) * (a + o - da);
score += (b + o - db) * (b + o - db);
score += (c + o - dc) * (c + o - dc);
score += (d + o - dd) * (d + o - dd);
// todo what is this second half
//if (score < key_dist[j]) // || (score == key_dist[j] && !o))
if (score < key_dist[j] || (score == key_dist[j] && !o))
{
if (score == 0) {
// printf("pants\n");
}
key_dist[j] = score;
key_lookup[j] = choice;
key_offset[j] = o;
}
}
}
}
}
}
}
for(int i = 0; i < 32; i++)
{
assert(key_dist[sorted_keys[i]] == 0);
}
uint t = 0;
for(int i = 0; i < 0x100000; i++) {
if (!key_dist[i]) {
// printf("%d %d %d\n", i, key_offset[i], key_dist[i]);
t++;
}
if (key_offset[i]) {
//printf("%d %d %d\n", i, key_offset[i], key_dist[i]);
}
}
// assert(t == 32);
assert(t == 128); // todo why
}
uint32_t counts[4] = {0,0,0,0};
uint8_t *d = &dest[0];
line_offsets.clear();
for(uint y=0; y < h; y+=2) {
uint8_t *base = &source[y * w * 3];
uint8_t *base2 = base + w * 3;
line_offsets.push_back(d - &dest[0]);
for(uint x = 0; x < w; x+= 2) {
uint rmin = std::min({base[0], base[3], base2[0], base2[3]});
uint gmin = std::min({base[1], base[4], base2[1], base2[4]});
uint bmin = std::min({base[2], base[5], base2[2], base2[5]});
uint rkey = get_key(rmin, base[0], base[3], base2[0], base2[3]);
uint16_t rdist = key_dist[rkey];
uint8_t rval = key_lookup[rkey];
uint gkey = get_key(gmin, base[1], base[4], base2[1], base2[4]);
uint16_t gdist = key_dist[gkey];
uint8_t gval = key_lookup[gkey];
uint bkey = get_key(bmin, base[2], base[5], base2[2], base2[5]);
uint16_t bdist = key_dist[bkey];
uint8_t bval = key_lookup[bkey];
uint16_t dist = std::max({rdist, gdist, bdist});
uint16_t val = std::min({rval, gval, bval});
// if (dist) {
// if (rdist > 4 || gdist > 4 || bdist > 4) {
if (rdist > max_rdist || gdist > max_gdist || bdist > max_bdist) {
if (use_56bit_raw)
{
// #2 : encode ABCD raw as 555, 454, 454, 454 in 7 bytes
//
// | Ga1 Ga0 "1" Ra4 : Ra3 Ra2 Ra1 Ra0
// | Ba4 Ba3 Ba2 Ba1 : Ba0 Ga4 Ga3 Ga2
//
// | Gb1 Gb0 "0" Rb4 : Rb3 Rb2 Rb1 Bd1
// | Bb4 Bb3 Bb2 Bb1 : Bd2 Gb4 Gb3 Gb2
//
// | Gc1 Gc0 Bd3 Rc4 : Rc3 Rc2 Rc1 Gd3
// | Bc4 Bc3 Bc2 Bc1 : Gd4 Gc4 Gc3 Gc2
//
// | Gd1 Gd0 (Gd2^Gd4) Rd4 : Rd3 Rd2 Rd1 Bd4
// | Bd4 Bd3 Bd2 Bd1 : Gd4 Gd3 Gd2
uint32_t lo = 0x0020u | rgb16(base[0], base[1], base[2]) | (rgb16(base[3], base[4], base[5]) << 16u);
uint32_t hi = rgb16(base2[0], base2[1], base2[2]) | (rgb16(base2[3], base2[4], base2[5]) << 16u);
// from "0" Bd4 Bd3 Bd2 : Bd1 Bd0 Gd4 Gd3
*d++ = lo;
*d++ = lo >> 8u;
*d++ = ((lo >> 16u) & ~1u) | ((hi & 0x10000000u)?1u:0);
*d++ = ((lo >> 24u) & ~8u) | ((hi & 0x20000000u)?8u:0);
*d++ = (hi & ~ 0x21u) | ((hi & 0x40000000u)?0x20u:0) | ((hi & 0x02000000u)?1u:0);
*d++ = ((hi >> 8u) & ~0x8u) | ((hi & 0x04000000u)?0x8u:0);
*d++ = ((hi >> 16u) & ~0x21u) | ((((hi & 0x01000000u)?1u:0)^((hi & 0x04000000u)?1u:0))?0x20u:0) | ((hi & 0x80000000u)?1u:0);
counts[3]++;
} else
{
// #1 : encode ABCD raw as 555, 555, 555, 555 in 8 bytes
//
// | Ga1 Ga0 "1" Ra4 : Ra3 Ra2 Ra1 Ra0
// | Ba4 Ba3 Ba2 Ba1 : Ba0 Ga4 Ga3 Ga2
// | Ga1 Ga0 "1" Ra4 : Ra3 Ra2 Ra1 Ra0
// | Ba4 Ba3 Ba2 Ba1 : Ba0 Ga4 Ga3 Ga2
// | Ga1 Ga0 "0" Ra4 : Ra3 Ra2 Ra1 Ra0
// | Ba4 Ba3 Ba2 Ba1 : Ba0 Ga4 Ga3 Ga2
// | Ga1 Ga0 "0" Ra4 : Ra3 Ra2 Ra1 Ra0
// | Ba4 Ba3 Ba2 Ba1 : Ba0 Ga4 Ga3 Ga2
//
write_word(d, 0x0020u | rgb16(base[0], base[1], base[2]));
write_word(d, 0x0020u | rgb16(base[3], base[4], base[5]));
write_word(d, rgb16(base2[0], base2[1], base2[2]));
write_word(d, rgb16(base2[3], base2[4], base2[5]));
counts[0]++;
}
} else if (val < 0x80) {
// #1 encode 555 color, then 1 of 32 2x2 add patterns for each component in 4 bytes total
assert(rval && gval && bval);
// | Ga1 Ga0 "0" Ra4 : Ra3 Ra2 Ra1 Ra0
// | Ba4 Ba3 Ba2 Ba1 : Ba0 Ga4 Ga3 Ga2
// |
// | Gp1 Gp0 Rp4 Rp3 : Rp2 Rp1 Rp0 "1"
// | Bp4 Bp3 Bp2 Bp1 : Bp0 Gp4 Gp3 Gp2
write_word(d, rgb16(rmin-key_offset[rkey], gmin - key_offset[gkey], bmin - key_offset[bkey]));
write_word(d, 1u | ((rgb15((rval - 1u)<<3u, (gval -1u)<<3u, (bval-1u)<<3u)) << 1u));
counts[1]++;
} else {
// #1 encode 555 color, then 1 of 4 2x2 add patterns for each component in 3 bytes total
// | Ga1 Ga0 "0" Ra4 : Ra3 Ra2 Ra1 Ra0
// | Ba4 Ba3 Ba2 Ba1 : Ba0 Ga4 Ga3 Ga2
// |
// | Bp1 Bp0 Gp1 Gp0 : Rp1 Rp0 "0" "0'
write_word(d, rgb16(rmin-key_offset[rkey], gmin - key_offset[gkey], bmin - key_offset[bkey]));
*d++ = ((bval - 0x81u) << 6u) | ((gval - 0x81u) << 4u) | ((rval - 0x81u) << 2u);
counts[2]++;
}
base += 6;
base2 += 6;
}
while (3u & (uintptr_t)d) *d++ = 0; // need aligned rows
for(int i=0;i<extra_line_words;i++) {
*d++ = 0;
*d++ = 0;
*d++ = 0;
*d++ = 0;
}
}
line_offsets.push_back(d - &dest[0]);
while (d < dest.end().base()) *d++ = 0;
//return counts[0]*8 + counts[3] * 7 + counts[1] *3 + counts[2] * 4;
int cost = counts[0]*8 + counts[3] * 7 + counts[2] *3 + counts[1] * 4;
min_cost = std::min(cost, min_cost);
bool rc = cost > max_cost;
max_cost = std::max(cost, max_cost);
total_cost += cost;
total_vals += w*h;
// printf("%s %d*8 %d*7 %d*4 %d*3\n", name, counts[0], counts[3], counts[1], counts[2]);
return rc;
}
#endif
void write_hword(uint word, FILE *out) {
assert(word < 0x10000);
fputc(word & 0xff, out);
fputc(word >> 8, out);
}
uint32_t pad_sector(uint32_t sector_offset, FILE *out) {
assert(sector_offset < 512);
if (sector_offset) for (;sector_offset < 512; sector_offset++) fputc(0, out);
return 0;
}
uint8_t to_bcd(uint x) {
assert(x<100);
return (x/10)*16 + (x%10);
}
#define PLAT_MAJOR 0
#define PLAT_MINOR 60
struct frame_header {
uint32_t mark0;
uint32_t mark1;
uint32_t magic;
uint8_t major, minior, debug, spare;
uint32_t sector_number; // relative to start of stream
uint32_t frame_number;
uint8_t hh, mm, ss, ff; // good old CD days (bcd)
uint32_t header_words;
uint16_t width;
uint16_t height;
uint32_t image_words;
uint32_t audio_words; // just to confirm really
uint32_t audio_freq;
uint8_t audio_channels; // always assume 16 bit
uint8_t pad[3];
uint32_t unused[4]; // little space for expansion
uint32_t total_sectors;
uint32_t last_sector;
// 1, 2, 4, 8 frame increments
uint32_t forward_frame_sector[4];
uint32_t backward_frame_sectors[4];
// h/2 + 1 row_offsets, last one should == image_words
uint16_t row_offsets[];
} __attribute__((packed));
int encode_movie(const char *filename, const char *audio_filename, const char *filename_out, int start_frame) {
worst_frame = 0;
total_cost = 0;
max_cost = 0;
min_cost = 0x7fffffff;
total_vals = 0;
#ifdef ADD_EOR_DEBUGGING
uint extra_line_words = 1;
#else
uint extra_line_words = 0;
#endif
unsigned error;
uint w = 320;
uint h = 240;
assert(!(h&1));
size_t size3 = w * h * 3;
size_t size2 = w * h * 2;
std::vector<uint32_t> frame_sectors;
std::vector<unsigned char> source;
std::vector<unsigned char> dest;
std::vector<uint32_t> line_offsets;
line_offsets.resize(120);
source.resize(size3);
dest.resize(size2);
FILE *file = fopen(filename, "rb");
FILE *audio_file = audio_filename ? fopen(audio_filename, "rb") : nullptr;
FILE *file_out = filename_out ? fopen(filename_out, "wb") : nullptr;
if (!file) {
fprintf(stderr, "Couldn't open input rgb file %s\n", filename);
return -1;
}
if (audio_filename && !audio_file) {
fprintf(stderr, "Couldn't open input pcm file %s\n", audio_filename);
return -1;
}
if (filename_out && !file_out) {
fprintf(stderr, "Couldn't open output pl2 file %s\n", filename_out);
return -1;
}
fseek(file, 0, SEEK_END);
size_t frames = ftell(file) / size3;
printf("Frame count %ld = %02ld:%02ld:%02ld\n", frames, (frames / (3600 * 30)) % 60, (frames / (60 * 30)) % 60, (frames / 30) % 60);
fseek(file, start_frame * size3, SEEK_SET);
// frames = 10000; // movie
uint32_t worst_length = 0;
int lcount=0;
int blcount=0;
int fbmax=0;
int bfcount=0;
uint32_t sector_offset = 0;
// todo i don't remember what the lineage of these were
// const int max_rdist = 8;
// const int max_gdist = 6;
// const int max_bdist = 8;
const int max_rdist = 4;
const int max_gdist = 4;
const int max_bdist = 4;
for (int i = 0; i<frames;i++)
{
if (1 != fread(&source[0], size3, 1, file)) {
fprintf(stderr, "Error reading frame %d\n", i);
return -1;
}
dither_image(w, h, source);
if (compress_image(NULL, w, h, source, dest, line_offsets, max_rdist, max_gdist, max_bdist, extra_line_words))
{
worst_frame = i;
}
int fb = 0;
uint32_t l = 0;
for(uint32_t o : line_offsets)
{
int len = o - l;
if (len > 1024)
{
blcount++;
fb++;
}
lcount++;
if (len > worst_length)
{
worst_length = len;
}
l = o;
}
#ifdef ADD_EOR_DEBUGGING
for(int r = 0; r < h/2; r++) {
int32_t o = line_offsets[r+1] - 4;
assert(o > 0);
assert(o < dest.size() - 4);
assert(!(o&3u));
// we cost 4 bytes per row, but we can use this instead of CRC to detect bad data (and also
// help with debugging, since we get a good idea what row the data belongs to)
dest[o + 0] = 0xaa;
dest[o + 1] = o + 1;
dest[o + 2] = i;
dest[o + 3] = r;
}
#endif
fbmax = std::max(fb, fbmax);
if (fb) bfcount++;
if (file_out)
{
size_t secoff = ftell(file_out);
uint32_t sector_num = secoff / 512;
frame_sectors.push_back(sector_num);
struct frame_header header;
static_assert(sizeof(header) <= 512);
memset(&header, 0, sizeof(header));
header.mark0 = header.mark1 = 0xffffffff;
header.magic = ('T' << 24) | ('A' << 16) | ('L' << 8) | 'P';
header.major = PLAT_MAJOR;
header.minior = PLAT_MINOR;
#ifdef ADD_EOR_DEBUGGING
header.debug = 1;
#endif
header.sector_number = sector_num;
header.frame_number = i;
int n = i + start_frame;
header.hh = to_bcd(n / (60 * 60 * 30));
header.mm = to_bcd((n / (60 * 30)) % 60);
header.ss = to_bcd((n / 30) % 60);
header.ff = to_bcd(n % 30);
header.header_words = ((sizeof(header) + (h+1) + 1) + 3) / 4;
assert(header.header_words <= 128);
for(int f=0;f<4;f++) {
header.forward_frame_sector[f] = header.backward_frame_sectors[f] = 0xffffffff;
}
header.width = w;
header.height = h;
header.image_words = line_offsets[h / 2] >> 2;
if (audio_file) {
header.audio_freq = 44100;
header.audio_channels = 2;
header.audio_words = (header.audio_freq * 2 * header.audio_channels / 30) / 4;
} else {
header.audio_freq = header.audio_channels = header.audio_words = 0;
}
fwrite(&header, 1, sizeof(header), file_out);
sector_offset += sizeof(header);
// note there is one extra for the end
for(uint y = 0; y <= h / 2; y++)
{
uint off = line_offsets[y];
assert(!(3u & off));
off >>= 2;
write_hword(off, file_out);
sector_offset += 2;
}
assert(sector_offset <= 0x200);
sector_offset = pad_sector(sector_offset, file_out);
if (audio_file)
{
assert(header.audio_words * 30 * 4 ==
2 * header.audio_channels * header.audio_freq); // make sure we divide correctly
uint32_t buf[header.audio_words];
uint audio_size_bytes = header.audio_words * 4;
fseek(audio_file, audio_size_bytes * (size_t) i, SEEK_SET);
if (1 != fread(buf, audio_size_bytes, 1, audio_file)) {
fprintf(stderr, "Error reading audio for frame %d\n", i);
return -1;
}
fwrite(buf, audio_size_bytes, 1, file_out);
sector_offset = pad_sector(audio_size_bytes & 0x1ff, file_out);
}
uint32_t actual_size = line_offsets[h / 2];
fwrite(&dest[0], 1, actual_size, file_out);
sector_offset = pad_sector(actual_size & 0x1ff, file_out);
}
if (!(i%60))
{
printf("%02d:%02d:%02d\n", (i / (3600 * 30)) % 60, (i / (60 * 30)) % 60, (i / 30) % 60);
}
}
uint32_t total_sectors = ftell(file_out) / 512;
fclose(file);
assert(frames == frame_sectors.size());
for(int i=0; i<frames; i++) {
fseek(file_out, 512 * ((uint64_t)frame_sectors[i]) + offsetof(frame_header, total_sectors), SEEK_SET);
fwrite(&total_sectors, 4, 1, file_out);
uint32_t v;
v = frame_sectors[frames-1]; fwrite(&v, 4, 1, file_out);
v = i < frames - 1 ? frame_sectors[i+1] : 0xffffffff; fwrite(&v, 4, 1, file_out);
v = i < frames - 2 ? frame_sectors[i+2] : 0xffffffff; fwrite(&v, 4, 1, file_out);
v = i < frames - 4 ? frame_sectors[i+4] : 0xffffffff; fwrite(&v, 4, 1, file_out);
v = i < frames - 8 ? frame_sectors[i+8] : 0xffffffff; fwrite(&v, 4, 1, file_out);
v = i >= 1 ? frame_sectors[i-1] : 0xffffffff; fwrite(&v, 4, 1, file_out);
v = i >= 2 ? frame_sectors[i-2] : 0xffffffff; fwrite(&v, 4, 1, file_out);
v = i >= 4 ? frame_sectors[i-4] : 0xffffffff; fwrite(&v, 4, 1, file_out);
v = i >= 8 ? frame_sectors[i-8] : 0xffffffff; fwrite(&v, 4, 1, file_out);
}
if (audio_file) fclose(audio_file);
if (file_out) fclose(file_out);
printf("last frame at %d\n", frame_sectors[frames-1]);
printf("Worst frame %d mic %d mac %d avg %d maxl %d\n", worst_frame, min_cost, max_cost, (int)((total_cost * w * (long)h) / total_vals), worst_length);
printf("%d %d %d, fbmax %d bfc %d/%ld\n", blcount, lcount, (int)(100l * blcount / lcount), fbmax, bfcount, frames);
return 0;
}
int main(int argc, char **argv) {
if (argc != 4) {
fprintf(stderr, "usage: convert <rgb_file> <pcm_file> <output_file.pl2>\n");
return -1;
}
return encode_movie(argv[1], argv[2], argv[3], 0);
}