Skip to content

Commit 39f4e07

Browse files
committed
Merge branch 'master' of https://github.com/cnlohr/vpxcoding
2 parents 1c54203 + 7c33198 commit 39f4e07

6 files changed

Lines changed: 135 additions & 26 deletions

File tree

.github/workflows/build.yml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
name: Build Test
2+
3+
on:
4+
push:
5+
pull_request:
6+
7+
jobs:
8+
Build-for-Linux:
9+
runs-on: ubuntu-latest
10+
steps:
11+
- uses: actions/checkout@v4
12+
with:
13+
submodules: recursive
14+
- name: Install more dependencies
15+
run: |
16+
sudo apt-get install -y \
17+
make \
18+
build-essential
19+
- name: Build
20+
run: make test

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ bittester : bittester.c
44
gcc -o $@ $^ -O2 -g
55

66
optimalfinder : optimalfinder.c
7-
gcc -o $@ $^ -O2 -g
7+
gcc -o $@ $^ -Os -g
88

99
test : bittester
1010
./bittester

README.md

Lines changed: 53 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,48 @@
1-
# vpxcoding single-file-header library.
1+
# vpxcoding single-file-header C library
22

3-
Single file header form of the arithmetic coder from [libvpx](https://github.com/webmproject/libvpx) as a general purpose compression/decompression of bitstreams algorithm.
3+
**WIP Note** - This offshoot has not been battle-hardened, and is subject to change. Also, hopefully in time there will be more complete, practical examples.
4+
5+
Single file header form of the range coder from [libvpx](https://github.com/webmproject/libvpx) (From the video codec VP8/VP9) as a general purpose compression/decompression of bitstreams algorithm. [Range Coding](https://en.wikipedia.org/wiki/Range_coding) is a type of [Arithmatic Coding](https://en.wikipedia.org/wiki/Arithmetic_coding), able to offer even better compression than the provably optmal [Huffman Coding](https://en.wikipedia.org/wiki/Huffman_coding) because it can represent symbols using partaial numbers of bits.
46

57
The idea of this coding is, given:
68

79
1. A bitstream
810
2. Knowledge about how likely the next bit is to be a 0 or 1 (written as a probability from 0..255)
911

10-
You can optimally code an output bitstream, with compression better than huffman trees by using arithmetic coding. Please note this is **not** a replacement for something like lz77, zstd, zlib, etc. But **is** a replacement for huffman coding. This ONLY covers optimal symbol expression. If there is data-similarity that must be compressed by another algorithm.
12+
You can optimally code an output bitstream, with compression better than huffman trees by using arithmetic coding. Please note this is **not** a replacement for something like lz77, zstd, zlib, etc. But **is** a replacement for huffman coding. This ONLY covers optimal symbol expression. If there is data-similarity that must be compressed by another algorithm. In general, you will want to get rid of whatever entropy you can before applying this compression technique. I.e. you can't just use this to compress text. If you are looking for something for that, you may want to consider my [heatshrink single-file-header](https://github.com/cnlohr/heatshrink-sfh).
13+
14+
It's also reaonsably fast. Not great, but not bad.
15+
16+
```
17+
Input Len: 16777216 bytes
18+
Output Len: 14104056 bytes
19+
Relative Size: 84.07 %
20+
Matching 16777216 bytes
21+
Encode Time: 375.116ms (42.653 MBytes/s)
22+
Decode Time: 537.677ms (29.758 MBytes/s)
23+
```
24+
(on a AMD Ryzen 7 5800X, GCC 11.4.0, -O2)
25+
26+
Also, the code is very small, about 768 bytes each for reading and writing when compiled. (below, using -Os) x64.
27+
```
28+
.rodata 0100 (256 bytes) vpx_norm // Table used for both encode and decode
29+
30+
.text 003f (63 bytes) vpx_start_encode
31+
.text 00e4 (228 bytes) vpx_write
32+
.text 005e (94 bytes) vpx_stop_encode
1133
12-
In general, you will want to get rid of whatever entropy you can before applying this compression technique.
34+
.text 0073 (115 bytes) vpx_read
35+
.text 00fa (250 bytes) vpx_reader_fill
36+
.text 003f (35 bytes) vpx_reader_find_end
37+
.text 0066 (102 bytes) vpx_reader_init
38+
.text 0016 (22 bytes) vpx_reader_has_error
39+
```
40+
41+
If you are on a platform that supports `__builtin_clz`, then you may want to define `VPXCODING_NOTABLE` as that will replace the table call with a `clz` and `andi` operation, which may be faster, and use less cache/RAM. If you are on a RAM constrained system, you may want to do this as well, but see the note in the header file about the manually unwound log2.
42+
43+
In my tests, depending on the application, this seems to be able to save between 1-5% over huffman trees. But, notably, there are situations where you can use this to much greater effect and simplicity than huffman trees (but not all situations).
44+
45+
## Example
1346

1447
It's very simple, if you have a bitstream you want to encode, you can write something like:
1548

@@ -66,11 +99,13 @@ NOTE: This is found emperically. It may not be correct or as-designed.
6699

67100
## Overall Properties
68101

69-
![Overall](https://private-user-images.githubusercontent.com/2748168/398571327-1ef7e391-2c7e-4f97-8a4e-cff0c53cc818.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MzUxMjkyNDIsIm5iZiI6MTczNTEyODk0MiwicGF0aCI6Ii8yNzQ4MTY4LzM5ODU3MTMyNy0xZWY3ZTM5MS0yYzdlLTRmOTctOGE0ZS1jZmYwYzUzY2M4MTgucG5nP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI0MTIyNSUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNDEyMjVUMTIxNTQyWiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9OTFkOTgyMTY5MjJhNjk0OTNjM2VhMGI2ZDMxYzVkNTIyN2QwMmY0ZWQ1NDlmMWU1MWNhNTZmNGVkNDI3YmQxNiZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QifQ.IX-AwtKMzUBppi4N1R1tnjksyofYlQKl0izo2jKk1Eg)
102+
![Optimal Compression Ratio](https://github.com/user-attachments/assets/02b9d48f-497c-4633-87b8-42a0e345aeaa)
70103

71-
![Edges](https://private-user-images.githubusercontent.com/2748168/398571325-ded30a7a-e449-4da4-865c-d450513f0139.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MzUxMjkyNDIsIm5iZiI6MTczNTEyODk0MiwicGF0aCI6Ii8yNzQ4MTY4LzM5ODU3MTMyNS1kZWQzMGE3YS1lNDQ5LTRkYTQtODY1Yy1kNDUwNTEzZjAxMzkucG5nP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI0MTIyNSUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNDEyMjVUMTIxNTQyWiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9YTJmM2E4YmE3NGQ1Y2EwZDFhYTk2N2Q1ZmIyMWQ5ODJmZTE1ZTZhZmQyNTM2ZTA1ODI4YjAxNWE5YzExMDgxYyZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QifQ.MEo910d7UEcy97vSx2zMIF6cBCvuRbwpt2yofZA4ROM)
104+
![Overall](https://github.com/user-attachments/assets/55d98d1d-9fc9-4bb2-a436-16dd0fbc603d)
72105

73-
![Optimal](https://private-user-images.githubusercontent.com/2748168/398571328-1c47a87f-d00f-4b7b-bf1e-1da297602786.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MzUxMjkyNDIsIm5iZiI6MTczNTEyODk0MiwicGF0aCI6Ii8yNzQ4MTY4LzM5ODU3MTMyOC0xYzQ3YTg3Zi1kMDBmLTRiN2ItYmYxZS0xZGEyOTc2MDI3ODYucG5nP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI0MTIyNSUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNDEyMjVUMTIxNTQyWiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9OTc3ODllOTVmZmU4MzJjZGNkNGVhOWEwMjBkNjM5MGViMGUxNjM2NDc1OTk4ZmMwYjQwNTNkZTA2OTFkNmNhMyZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QifQ.P3PlCYdhjuegYD7KD6o_LoAldsuhj8dNmVlHC33C6AQ)
106+
![Edges](https://github.com/user-attachments/assets/c18f296a-d2af-4d7d-84a3-ef145f01a66c)
107+
108+
![Optimal](https://github.com/user-attachments/assets/d2315457-68a6-460e-aaa2-73ba25c0b0aa)
74109

75110

76111
## Special Thanks
@@ -82,3 +117,14 @@ Whoever the original author actually was. At the very least thank you for makin
82117
* https://github.com/danielrh/arithmetic_coding_tutorial
83118
* https://github.com/danielrh/losslessh264
84119

120+
* This video is a GREAT introduction to Huffman, Arithmetic Coding, And ANS [Better than Huffman](https://www.youtube.com/watch?v=RFWJM8JMXBs)
121+
122+
These two youtube videos were really good, but don't explain this specific implementation.
123+
* [(IC 5.1) Arithmetic coding - introduction](https://www.youtube.com/watch?v=ouYV3rBtrTI)
124+
* [(IC 5.2) Arithmetic coding - Example #1](https://www.youtube.com/watch?v=7vfqhoJVwuc)
125+
* [(IC 5.3) Arithmetic coding - Example #2](https://www.youtube.com/watch?v=CXCWQy9N2ag)
126+
* [(IC 5.4) Why the interval needs to be completely contained](https://www.youtube.com/watch?v=jHS8-rmEo5k)
127+
* [(IC 5.5) Rescaling operations for arithmetic coding](https://www.youtube.com/watch?v=t8_198HHSfI)
128+
* This continues on for 13? episodes?
129+
130+

bittester.c

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
#define VPXCODING_READER
66
#define VPXCODING_WRITER
7+
#define VPXCODING_NOTABLE
78
#include "vpxcoding.h"
89

910
double GetAbsoluteTime()
@@ -14,9 +15,29 @@ double GetAbsoluteTime()
1415
}
1516

1617
uint8_t dummydata[1024*1024*16];
18+
19+
//#define TEST_RANDOM_PROBS
20+
21+
#ifndef TEST_RANDOM_PROBS
22+
const int arbitrary_prob = 187;
23+
#else
24+
uint8_t dummyprobs[1024*1024*16*8];
25+
#endif
26+
1727
int main( int argc, char ** argv )
1828
{
1929
int i;
30+
#if 0
31+
for( i = 0; i < 256; i++ )
32+
{
33+
printf( "%2d", VPXCODING_VPXNORM( i ) );
34+
if( ( i & 15 ) == 15 )
35+
{
36+
printf( "\n" );
37+
}
38+
}
39+
return 0;
40+
#endif
2041
for( i = 0; i < sizeof(dummydata); i++ )
2142
{
2243
//dummydata[i] = rand();
@@ -27,18 +48,22 @@ int main( int argc, char ** argv )
2748
uint8_t * bufferO = malloc(sizeof(dummydata)*20);
2849
vpx_start_encode( &w, bufferO, sizeof(dummydata)*20);
2950

30-
const int arbitrary_prob = 187;
31-
3251
double startEnc = GetAbsoluteTime();
52+
int bitno = 0;
3353
for( i = 0; i < sizeof(dummydata); i++ )
3454
{
3555
int data = dummydata[i];
3656
int bits = 8;
3757
int bit;
3858
for (bit = bits - 1; bit >= 0; bit--)
3959
{
60+
#ifdef TEST_RANDOM_PROBS
61+
int prob = dummyprobs[bitno++];
62+
#else
63+
int prob = arbitrary_prob;
64+
#endif
4065
int outbit = (data >> bit) & 1;
41-
vpx_write(&w, outbit, arbitrary_prob);
66+
vpx_write(&w, outbit, prob);
4267
}
4368
}
4469
vpx_stop_encode(&w);
@@ -56,13 +81,20 @@ int main( int argc, char ** argv )
5681
vpx_reader reader;
5782
int ret = vpx_reader_init(&reader, bufferO, w.pos, 0, 0 );
5883
double startDec = GetAbsoluteTime();
84+
bitno = 0;
5985
for( i = 0; i < sizeof(dummydata); i++ )
6086
{
6187
int bits = 8;
6288
int bit;
6389
uint8_t data = 0;
90+
#ifdef TEST_RANDOM_PROBS
91+
int prob = dummyprobs[bitno++] = rand()&0xff;
92+
#else
93+
int prob = arbitrary_prob;
94+
#endif
95+
6496
for (bit = bits - 1; bit >= 0; bit--)// Arbitrary, for testing
65-
data = (data<<1) | vpx_read(&reader, arbitrary_prob);
97+
data = (data<<1) | vpx_read(&reader, prob);
6698
if( data != dummydata[i] )
6799
{
68100
fprintf( stderr, "Disagree at %d (%08x != %08x)\n", i, data, dummydata[i] );
@@ -73,7 +105,7 @@ int main( int argc, char ** argv )
73105

74106
printf( "Matching %d bytes\n", i );
75107
printf( "Encode Time: %.3fms (%.3f MBytes/s)\n", (endEnc - startEnc)*1000.0, (sizeof(dummydata)/1024/1024)/(endEnc - startEnc) );
76-
printf( "Deccode Time: %.3fms (%.3f MBytes/s)\n", (endDec - startDec)*1000.0, (sizeof(dummydata)/1024/1024)/(endDec - startDec) );
108+
printf( "Decode Time: %.3fms (%.3f MBytes/s)\n", (endDec - startDec)*1000.0, (sizeof(dummydata)/1024/1024)/(endDec - startDec) );
77109

78110
return 0;
79111
}

optimalfinder.c

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22
#include <stdio.h>
33
#include <sys/time.h>
44

5+
#define VPXCODING_DECORATOR
56
#define VPXCODING_READER
67
#define VPXCODING_WRITER
8+
#define VPXCODING_IMPLEMENTATION
79
#include "vpxcoding.h"
810

911
uint8_t dummydata[1024*64];
@@ -12,14 +14,18 @@ int main( int argc, char ** argv )
1214
{
1315
srand( 1 );
1416
int i;
15-
printf( "Probability of 1," );
17+
FILE * fOpt = fopen( "FullList.csv", "w" );
18+
FILE * fOptPar = fopen( "FullListPar.csv", "w" );
19+
fprintf( fOpt, "Probability of 1," );
1620
for( int probability = 0; probability < 256; probability++ )
1721
{
18-
printf( "%d,", probability );
22+
fprintf( fOpt, "%d,", probability );
1923
}
20-
printf( "Best\n" ); double percentones = 0.0;
24+
fprintf( fOpt, "Best\n" ); double percentones = 0.0;
2125
for( percentones = 0.0; percentones < 100; percentones+=0.1 )
2226
{
27+
printf( "%f\n", percentones );
28+
2329
memset( dummydata, 0, sizeof( dummydata ) );
2430
for( i = 0; i < sizeof(dummydata); i++ )
2531
{
@@ -33,7 +39,7 @@ int main( int argc, char ** argv )
3339
}
3440
}
3541

36-
printf( "%.1f%%,",percentones);
42+
fprintf( fOpt, "%.1f%%,",percentones);
3743
int bestprob;
3844
double bestrate;
3945
bestprob = 0;
@@ -62,7 +68,7 @@ int main( int argc, char ** argv )
6268
}
6369
//printf( "Relative Size: %.2f %%\n", w.pos * 100.0 / sizeof(dummydata) );
6470
double rate = w.pos * 100.0 / sizeof(dummydata);
65-
printf( "%.4f,", rate );
71+
fprintf( fOpt, "%.4f,", rate );
6672
if( bestrate > rate )
6773
{
6874
bestrate = rate;
@@ -85,7 +91,8 @@ int main( int argc, char ** argv )
8591
}
8692
}
8793
}
88-
printf( "%d\n",bestprob );
94+
fprintf( fOpt, "%d\n",bestprob );
95+
fprintf( fOptPar, "%f,%f,%d\n", percentones, bestrate, bestprob );
8996
}
9097

9198

vpxcoding.h

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/*
22
* Copyright (c) 2010 The WebM project authors. All Rights Reserved.
3-
* Amalgam is (c) 2024 cnlohr
3+
* Amalgam is (c) 2024,2025 cnlohr
44
*
55
* Use of this source code is governed by a BSD-style license
66
* that can be found in the LICENSE file in the root of the libvpx
@@ -15,6 +15,8 @@
1515
* This amalgam has some notable changes:
1616
* 1. Changed decrypt_state / decrypt_cb to ingest for reader.
1717
* 2. Removed all libvpx dependencies.
18+
* 3. Changed vpx_norm to be configurable (For low-flash situations)
19+
* 4. Removed endian-specific code, and just iterated directly.
1820
*
1921
* To Use:
2022
@@ -32,7 +34,6 @@
3234
#include <stdint.h>
3335
#include <limits.h>
3436
#include <string.h>
35-
#include <endian.h>
3637

3738
#ifdef __cplusplus
3839
extern "C" {
@@ -44,7 +45,7 @@ extern "C" {
4445
#define VPXCODING_IMPLEMENTATION
4546
#endif
4647

47-
48+
#ifndef VPXCODING_CUSTOM_VPXNORM
4849
static const uint8_t vpx_norm[256] = {
4950
0, 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4,
5051
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
@@ -63,6 +64,7 @@ static const uint8_t vpx_norm[256] = {
6364
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
6465
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
6566
};
67+
#endif
6668

6769
#ifdef VPXCODING_READER
6870

@@ -175,12 +177,13 @@ VPXCODING_DECORATOR void vpx_reader_fill(vpx_reader *r)
175177
if (bits_left > BD_VALUE_SIZE) {
176178
const int bits = (shift & 0xfffffff8) + CHAR_BIT;
177179
BD_VALUE nv;
178-
BD_VALUE big_endian_values;
179-
memcpy(&big_endian_values, buffer, sizeof(BD_VALUE));
180+
BD_VALUE big_endian_values = 0;
181+
int n;
180182
#ifdef VPX_64BIT
181-
big_endian_values = htobe64(big_endian_values);
183+
// Formulated a little unusually, but selected by looking through different godbolt outputs, comparing this and |= (buffer[n]<<(56-n*8))
184+
for( n = 0; n < 8; n++ ) big_endian_values = (big_endian_values<<8) | buffer[n];
182185
#else
183-
big_endian_values = htobe32(big_endian_values);
186+
for( n = 0; n < 4; n++ ) big_endian_values = (big_endian_values<<8) | buffer[n];
184187
#endif
185188
nv = big_endian_values >> (BD_VALUE_SIZE - bits);
186189
count += bits;
@@ -459,3 +462,4 @@ VPXCODING_DECORATOR int vpx_stop_encode(vpx_writer *br) {
459462

460463
#endif
461464

465+

0 commit comments

Comments
 (0)