25static constexpr uint8_t DBZV_VERSION_HIGH = 0;
26static constexpr uint8_t DBZV_VERSION_LOW = 1;
27static constexpr uint8_t COMPRESSION_ZLIB = 1;
28static constexpr unsigned MAX_VECTOR = 16;
29static constexpr unsigned BLOCK_WIDTH = MAX_VECTOR;
30static constexpr unsigned BLOCK_HEIGHT = MAX_VECTOR;
31static constexpr unsigned FLAG_KEYFRAME = 0x01;
38static constexpr unsigned VECTOR_TAB_SIZE =
41 MAX_VECTOR * MAX_VECTOR - 2 * MAX_VECTOR;
43static constexpr auto vectorTable = [] {
44 std::array<CodecVector, VECTOR_TAB_SIZE> result = {};
51 for (
int i = 1; i <= int(MAX_VECTOR); ++i, p += 8) {
52 result[p + 0] = {int8_t( i), int8_t( 0)};
53 result[p + 1] = {int8_t(-i), int8_t( 0)};
54 result[p + 2] = {int8_t( 0), int8_t( i)};
55 result[p + 3] = {int8_t( 0), int8_t(-i)};
56 result[p + 4] = {int8_t( i), int8_t( i)};
57 result[p + 5] = {int8_t(-i), int8_t( i)};
58 result[p + 6] = {int8_t( i), int8_t(-i)};
59 result[p + 7] = {int8_t(-i), int8_t(-i)};
62 for (
int y = 1; y <= int(MAX_VECTOR / 2); ++y) {
63 for (
int x = 1; x <= int(MAX_VECTOR / 2); ++x) {
65 result[p + 0] = {int8_t( x), int8_t( y)};
66 result[p + 1] = {int8_t(-x), int8_t( y)};
67 result[p + 2] = {int8_t( x), int8_t(-y)};
68 result[p + 3] = {int8_t(-x), int8_t(-y)};
72 assert(p == VECTOR_TAB_SIZE);
75 auto compare = [](
const CodecVector& l,
const CodecVector& r) {
76 auto cost = [](
const CodecVector& v) {
77 auto c =
cstd::sqrt(
double(v.x * v.x + v.y * v.y));
78 if ((v.x == 0) || (v.y == 0)) {
90 return std::tuple(cost(l), l.x, l.y) <
91 std::tuple(cost(r), r.x, r.y);
108static inline void writePixel(
112 unsigned r = pixelOps.
red(pixel);
113 unsigned g = pixelOps.
green(pixel);
114 unsigned b = pixelOps.
blue(pixel);
115 dest = (r << 16) | (
g << 8) | b;
124 memset(&zstream, 0,
sizeof(zstream));
125 deflateInit(&zstream, 6);
146void ZMBVEncoder::setupBuffers()
148 static constexpr size_t pixelSize =
sizeof(
Pixel);
150 pitch = width + 2 * MAX_VECTOR;
151 auto bufSize = (height + 2 * MAX_VECTOR) * pitch * pixelSize + 2048;
158 outputSize = neededSize();
159 output.
resize(outputSize);
161 assert((width % BLOCK_WIDTH ) == 0);
162 assert((height % BLOCK_HEIGHT) == 0);
163 size_t xBlocks = width / BLOCK_WIDTH;
164 size_t yBlocks = height / BLOCK_HEIGHT;
165 blockOffsets.
resize(xBlocks * yBlocks);
166 for (
auto y :
xrange(yBlocks)) {
167 for (
auto x :
xrange(xBlocks)) {
168 blockOffsets[y * xBlocks + x] =
169 ((y * BLOCK_HEIGHT) + MAX_VECTOR) * pitch +
170 (x * BLOCK_WIDTH) + MAX_VECTOR;
175unsigned ZMBVEncoder::neededSize()
const
177 static constexpr unsigned pixelSize =
sizeof(
Pixel);
178 unsigned f = pixelSize * width * height + 2 * (1 + (width / 8)) * (1 + (height / 8)) + 1024;
182unsigned ZMBVEncoder::possibleBlock(
int vx,
int vy,
size_t offset)
185 const auto* pOld = &(std::bit_cast<const Pixel*>(oldFrame.
data()))[offset + (vy * pitch) + vx];
186 const auto* pNew = &(std::bit_cast<const Pixel*>(newFrame.
data()))[offset];
187 for (
unsigned y = 0; y < BLOCK_HEIGHT; y += 4) {
188 for (
unsigned x = 0; x < BLOCK_WIDTH; x += 4) {
189 if (pOld[x] != pNew[x]) ++ret;
197unsigned ZMBVEncoder::compareBlock(
int vx,
int vy,
size_t offset)
200 const auto* pOld = &(std::bit_cast<const Pixel*>(oldFrame.
data()))[offset + (vy * pitch) + vx];
201 const auto* pNew = &(std::bit_cast<const Pixel*>(newFrame.
data()))[offset];
202 repeat(BLOCK_HEIGHT, [&] {
203 for (
auto x :
xrange(BLOCK_WIDTH)) {
204 if (pOld[x] != pNew[x]) ++ret;
212void ZMBVEncoder::addXorBlock(
int vx,
int vy,
size_t offset,
unsigned& workUsed)
216 const auto* pOld = &(std::bit_cast<const Pixel*>(oldFrame.
data()))[offset + (vy * pitch) + vx];
217 const auto* pNew = &(std::bit_cast<const Pixel*>(newFrame.
data()))[offset];
218 repeat(BLOCK_HEIGHT, [&] {
219 for (
auto x :
xrange(BLOCK_WIDTH)) {
220 auto pXor = pNew[x] ^ pOld[x];
221 writePixel(pXor, *std::bit_cast<LE_P*>(&work[workUsed]));
222 workUsed +=
sizeof(
Pixel);
229void ZMBVEncoder::addXorFrame(
unsigned& workUsed)
231 auto* vectors = std::bit_cast<int8_t*>(&work[workUsed]);
233 unsigned xBlocks = width / BLOCK_WIDTH;
234 unsigned yBlocks = height / BLOCK_HEIGHT;
235 unsigned blockCount = xBlocks * yBlocks;
238 workUsed = (workUsed + blockCount * 2 + 3) & ~3;
242 for (
auto b :
xrange(blockCount)) {
243 auto offset = blockOffsets[b];
245 unsigned bestChange = compareBlock(bestVx, bestVy, offset);
246 if (bestChange >= 4) {
248 for (
const auto& v : vectorTable) {
249 if (possibleBlock(v.x, v.y, offset) < 4) {
250 if (
auto testChange = compareBlock(v.x, v.y, offset);
251 testChange < bestChange) {
252 bestChange = testChange;
253 bestVx = narrow<int>(v.x);
254 bestVy = narrow<int>(v.y);
255 if (bestChange < 4)
break;
258 if (possibles == 0)
break;
262 vectors[b * 2 + 0] = narrow<int8_t>(bestVx << 1);
263 vectors[b * 2 + 1] = narrow<int8_t>(bestVy << 1);
265 vectors[b * 2 + 0] |= 1;
266 addXorBlock(bestVx, bestVy, offset, workUsed);
271void ZMBVEncoder::addFullFrame(
unsigned& workUsed)
274 static constexpr size_t pixelSize =
sizeof(
Pixel);
277 &newFrame[pixelSize * (MAX_VECTOR + MAX_VECTOR * pitch)];
279 const auto* pixelsIn = std::bit_cast<const Pixel*>(readFrame);
280 auto* pixelsOut = std::bit_cast<LE_P*>(&work[workUsed]);
281 for (
auto x :
xrange(width)) {
282 writePixel(pixelsIn[x], pixelsOut[x]);
284 readFrame += pitch *
sizeof(
Pixel);
285 workUsed += narrow<unsigned>(width *
sizeof(
Pixel));
289const ZMBVEncoder::Pixel* ZMBVEncoder::getScaledLine(
const FrameSource* frame,
unsigned y,
Pixel* workBuf)
const
293 return frame->getLinePtr320_240(y, std::span<uint32_t, 320>(workBuf, 320)).
data();
295 return frame->getLinePtr640_480(y, std::span<uint32_t, 640>(workBuf, 640)).data();
297 return frame->getLinePtr960_720(y, std::span<uint32_t, 960>(workBuf, 960)).data();
305 std::swap(newFrame, oldFrame);
308 unsigned workUsed = 0;
309 unsigned writeDone = 1;
310 uint8_t* writeBuf = output.
data();
314 static const uint8_t ZMBV_FORMAT_32BPP = 8;
316 output[0] |= FLAG_KEYFRAME;
317 auto* header = std::bit_cast<KeyframeHeader*>(
318 writeBuf + writeDone);
319 header->high_version = DBZV_VERSION_HIGH;
320 header->low_version = DBZV_VERSION_LOW;
321 header->compression = COMPRESSION_ZLIB;
322 header->format = ZMBV_FORMAT_32BPP;
323 header->blockWidth = BLOCK_WIDTH;
324 header->blockHeight = BLOCK_HEIGHT;
326 deflateReset(&zstream);
330 static constexpr size_t pixelSize =
sizeof(
Pixel);
331 auto linePitch = pitch * pixelSize;
332 auto lineWidth = size_t(width) * pixelSize;
334 &newFrame[pixelSize * (MAX_VECTOR + MAX_VECTOR * pitch)];
335 for (
auto i :
xrange(height)) {
336 const auto* scaled = std::bit_cast<const uint8_t*>(
337 getScaledLine(frame, i, std::bit_cast<Pixel*>(dest)));
338 if (scaled != dest) memcpy(dest, scaled, lineWidth);
345 addFullFrame(workUsed);
348 addXorFrame(workUsed);
351 zstream.next_in = work.
data();
352 zstream.avail_in = workUsed;
353 zstream.total_in = 0;
355 zstream.next_out = std::bit_cast<Bytef*>(writeBuf + writeDone);
356 zstream.avail_out = outputSize - writeDone;
357 zstream.total_out = 0;
358 auto r = deflate(&zstream, Z_SYNC_FLUSH);
359 assert(r == Z_OK); (void)r;
361 return {output.
data(), writeDone + zstream.total_out};
Interface for getting lines from a video frame.
void resize(size_t size)
Grow or shrink the memory block.
const T * data() const
Returns pointer to the start of the memory buffer.
unsigned green(Pixel p) const
unsigned red(Pixel p) const
Extract RGBA components, each in range [0..255].
unsigned blue(Pixel p) const
ZMBVEncoder(unsigned width, unsigned height)
std::span< const uint8_t > compressFrame(bool keyFrame, const FrameSource *frame)
constexpr double sqrt(double x)
This file implemented 3 utility functions:
CharacterConverter::Pixel Pixel
constexpr void fill(ForwardRange &&range, const T &value)
constexpr void sort(RandomAccessRange &&range)
constexpr void repeat(T n, Op op)
Repeat the given operation 'op' 'n' times.
constexpr auto xrange(T e)