spice-common/common/quic.c
Frediano Ziglio 349a74d7c2 quic: Fix endianness encoding
The image is going to network and network protocol is little endian
so the numbers has to be little endian. Note that this is already done
during decoding.
Tested on a ppc64 machine.

Signed-off-by: Frediano Ziglio <fziglio@redhat.com>
Acked-by: Christophe Fergeau <cfergeau@redhat.com>
2018-06-04 16:05:20 +01:00

1370 lines
46 KiB
C

/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/*
Copyright (C) 2009 Red Hat, Inc.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, see <http://www.gnu.org/licenses/>.
*/
// Red Hat image compression based on SFALIC by Roman Starosolski
// http://sun.iinf.polsl.gliwice.pl/~rstaros/sfalic/index.html
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <glib.h>
#include "quic.h"
#include "spice_common.h"
#include "bitops.h"
/* ASCII "QUIC" */
#define QUIC_MAGIC 0x43495551
#define QUIC_VERSION_MAJOR 0U
#define QUIC_VERSION_MINOR 1U
#define QUIC_VERSION ((QUIC_VERSION_MAJOR << 16) | (QUIC_VERSION_MAJOR & 0xffff))
typedef uint8_t BYTE;
/* maximum number of codes in family */
#define MAXNUMCODES 8
/* model evolution, warning: only 1,3 and 5 allowed */
#define DEFevol 3
#define MINevol 0
#define MAXevol 5
/* starting wait mask index */
#define DEFwmistart 0
#define MINwmistart 0
/* codeword length limit */
#define DEFmaxclen 26
/* target wait mask index */
#define DEFwmimax 6
/* number of symbols to encode before increasing wait mask index */
#define DEFwminext 2048
#define MINwminext 1
#define MAXwminext 100000000
typedef struct QuicFamily {
unsigned int nGRcodewords[MAXNUMCODES]; /* indexed by code number, contains number of
unmodified GR codewords in the code */
unsigned int notGRcwlen[MAXNUMCODES]; /* indexed by code number, contains codeword
length of the not-GR codeword */
unsigned int notGRprefixmask[MAXNUMCODES]; /* indexed by code number, contains mask to
determine if the codeword is GR or not-GR */
unsigned int notGRsuffixlen[MAXNUMCODES]; /* indexed by code number, contains suffix
length of the not-GR codeword */
unsigned int golomb_code_len[256][MAXNUMCODES];
unsigned int golomb_code[256][MAXNUMCODES];
/* array for translating distribution U to L for depths up to 8 bpp,
initialized by decorrelate_init() */
BYTE xlatU2L[256];
/* array for translating distribution L to U for depths up to 8 bpp,
initialized by correlate_init() */
unsigned int xlatL2U[256];
} QuicFamily;
static QuicFamily family_8bpc;
static QuicFamily family_5bpc;
typedef unsigned COUNTER; /* counter in the array of counters in bucket of the data model */
typedef struct s_bucket {
COUNTER *pcounters; /* pointer to array of counters */
unsigned int bestcode; /* best code so far */
} s_bucket;
typedef struct Encoder Encoder;
typedef struct CommonState {
unsigned int waitcnt;
unsigned int tabrand_seed;
unsigned int wm_trigger;
unsigned int wmidx;
unsigned int wmileft;
int melcstate; /* index to the state array */
int melclen; /* contents of the state array location
indexed by melcstate: the "expected"
run length is 2^melclen, shorter runs are
encoded by a 1 followed by the run length
in binary representation, wit a fixed length
of melclen bits */
unsigned long melcorder; /* 2^ melclen */
} CommonState;
#define MAX_CHANNELS 4
typedef struct FamilyStat {
s_bucket **buckets_ptrs;
s_bucket *buckets_buf;
COUNTER *counters;
} FamilyStat;
typedef struct Channel {
int correlate_row_width;
BYTE *correlate_row;
s_bucket **_buckets_ptrs;
FamilyStat family_stat_8bpc;
FamilyStat family_stat_5bpc;
CommonState state;
} Channel;
struct Encoder {
QuicUsrContext *usr;
QuicImageType type;
unsigned int width;
unsigned int height;
unsigned int n_buckets_8bpc;
unsigned int n_buckets_5bpc;
unsigned int io_available_bits;
uint32_t io_word;
uint32_t io_next_word;
uint32_t *io_now;
uint32_t *io_end;
uint32_t io_words_count;
int rows_completed;
Channel channels[MAX_CHANNELS];
CommonState rgb_state;
};
/* bppmask[i] contains i ones as lsb-s */
static const unsigned int bppmask[33] = {
0x00000000, /* [0] */
0x00000001, 0x00000003, 0x00000007, 0x0000000f,
0x0000001f, 0x0000003f, 0x0000007f, 0x000000ff,
0x000001ff, 0x000003ff, 0x000007ff, 0x00000fff,
0x00001fff, 0x00003fff, 0x00007fff, 0x0000ffff,
0x0001ffff, 0x0003ffff, 0x0007ffff, 0x000fffff,
0x001fffff, 0x003fffff, 0x007fffff, 0x00ffffff,
0x01ffffff, 0x03ffffff, 0x07ffffff, 0x0fffffff,
0x1fffffff, 0x3fffffff, 0x7fffffff, 0xffffffff /* [32] */
};
#define bitat(n) (1u<<(n))
#define TABRAND_TABSIZE 256
#define TABRAND_SEEDMASK 0x0ff
static const unsigned int tabrand_chaos[TABRAND_TABSIZE] = {
0x02c57542, 0x35427717, 0x2f5a2153, 0x9244f155, 0x7bd26d07, 0x354c6052, 0x57329b28, 0x2993868e,
0x6cd8808c, 0x147b46e0, 0x99db66af, 0xe32b4cac, 0x1b671264, 0x9d433486, 0x62a4c192, 0x06089a4b,
0x9e3dce44, 0xdaabee13, 0x222425ea, 0xa46f331d, 0xcd589250, 0x8bb81d7f, 0xc8b736b9, 0x35948d33,
0xd7ac7fd0, 0x5fbe2803, 0x2cfbc105, 0x013dbc4e, 0x7a37820f, 0x39f88e9e, 0xedd58794, 0xc5076689,
0xfcada5a4, 0x64c2f46d, 0xb3ba3243, 0x8974b4f9, 0x5a05aebd, 0x20afcd00, 0x39e2b008, 0x88a18a45,
0x600bde29, 0xf3971ace, 0xf37b0a6b, 0x7041495b, 0x70b707ab, 0x06beffbb, 0x4206051f, 0xe13c4ee3,
0xc1a78327, 0x91aa067c, 0x8295f72a, 0x732917a6, 0x1d871b4d, 0x4048f136, 0xf1840e7e, 0x6a6048c1,
0x696cb71a, 0x7ff501c3, 0x0fc6310b, 0x57e0f83d, 0x8cc26e74, 0x11a525a2, 0x946934c7, 0x7cd888f0,
0x8f9d8604, 0x4f86e73b, 0x04520316, 0xdeeea20c, 0xf1def496, 0x67687288, 0xf540c5b2, 0x22401484,
0x3478658a, 0xc2385746, 0x01979c2c, 0x5dad73c8, 0x0321f58b, 0xf0fedbee, 0x92826ddf, 0x284bec73,
0x5b1a1975, 0x03df1e11, 0x20963e01, 0xa17cf12b, 0x740d776e, 0xa7a6bf3c, 0x01b5cce4, 0x1118aa76,
0xfc6fac0a, 0xce927e9b, 0x00bf2567, 0x806f216c, 0xbca69056, 0x795bd3e9, 0xc9dc4557, 0x8929b6c2,
0x789d52ec, 0x3f3fbf40, 0xb9197368, 0xa38c15b5, 0xc3b44fa8, 0xca8333b0, 0xb7e8d590, 0xbe807feb,
0xbf5f8360, 0xd99e2f5c, 0x372928e1, 0x7c757c4c, 0x0db5b154, 0xc01ede02, 0x1fc86e78, 0x1f3985be,
0xb4805c77, 0x00c880fa, 0x974c1b12, 0x35ab0214, 0xb2dc840d, 0x5b00ae37, 0xd313b026, 0xb260969d,
0x7f4c8879, 0x1734c4d3, 0x49068631, 0xb9f6a021, 0x6b863e6f, 0xcee5debf, 0x29f8c9fb, 0x53dd6880,
0x72b61223, 0x1f67a9fd, 0x0a0f6993, 0x13e59119, 0x11cca12e, 0xfe6b6766, 0x16b6effc, 0x97918fc4,
0xc2b8a563, 0x94f2f741, 0x0bfa8c9a, 0xd1537ae8, 0xc1da349c, 0x873c60ca, 0x95005b85, 0x9b5c080e,
0xbc8abbd9, 0xe1eab1d2, 0x6dac9070, 0x4ea9ebf1, 0xe0cf30d4, 0x1ef5bd7b, 0xd161043e, 0x5d2fa2e2,
0xff5d3cae, 0x86ed9f87, 0x2aa1daa1, 0xbd731a34, 0x9e8f4b22, 0xb1c2c67a, 0xc21758c9, 0xa182215d,
0xccb01948, 0x8d168df7, 0x04238cfe, 0x368c3dbc, 0x0aeadca5, 0xbad21c24, 0x0a71fee5, 0x9fc5d872,
0x54c152c6, 0xfc329483, 0x6783384a, 0xeddb3e1c, 0x65f90e30, 0x884ad098, 0xce81675a, 0x4b372f7d,
0x68bf9a39, 0x43445f1e, 0x40f8d8cb, 0x90d5acb6, 0x4cd07282, 0x349eeb06, 0x0c9d5332, 0x520b24ef,
0x80020447, 0x67976491, 0x2f931ca3, 0xfe9b0535, 0xfcd30220, 0x61a9e6cc, 0xa487d8d7, 0x3f7c5dd1,
0x7d0127c5, 0x48f51d15, 0x60dea871, 0xc9a91cb7, 0x58b53bb3, 0x9d5e0b2d, 0x624a78b4, 0x30dbee1b,
0x9bdf22e7, 0x1df5c299, 0x2d5643a7, 0xf4dd35ff, 0x03ca8fd6, 0x53b47ed8, 0x6f2c19aa, 0xfeb0c1f4,
0x49e54438, 0x2f2577e6, 0xbf876969, 0x72440ea9, 0xfa0bafb8, 0x74f5b3a0, 0x7dd357cd, 0x89ce1358,
0x6ef2cdda, 0x1e7767f3, 0xa6be9fdb, 0x4f5f88f8, 0xba994a3a, 0x08ca6b65, 0xe0893818, 0x9e00a16a,
0xf42bfc8f, 0x9972eedc, 0x749c8b51, 0x32c05f5e, 0xd706805f, 0x6bfbb7cf, 0xd9210a10, 0x31a1db97,
0x923a9559, 0x37a7a1f6, 0x059f8861, 0xca493e62, 0x65157e81, 0x8f6467dd, 0xab85ff9f, 0x9331aff2,
0x8616b9f5, 0xedbd5695, 0xee7e29b1, 0x313ac44f, 0xb903112f, 0x432ef649, 0xdc0a36c0, 0x61cf2bba,
0x81474925, 0xa8b6c7ad, 0xee5931de, 0xb2f8158d, 0x59fb7409, 0x2e3dfaed, 0x9af25a3f, 0xe1fed4d5,
};
static unsigned int stabrand(void)
{
SPICE_VERIFY( !(TABRAND_SEEDMASK & TABRAND_TABSIZE));
SPICE_VERIFY( TABRAND_SEEDMASK + 1 == TABRAND_TABSIZE );
return TABRAND_SEEDMASK;
}
static unsigned int tabrand(unsigned int *tabrand_seed)
{
return tabrand_chaos[++*tabrand_seed & TABRAND_SEEDMASK];
}
static const unsigned short besttrigtab[3][11] = { /* array of wm_trigger for waitmask and DEFevol,
used by set_wm_trigger() */
/* 1 */ { 550, 900, 800, 700, 500, 350, 300, 200, 180, 180, 160},
/* 3 */ { 110, 550, 900, 800, 550, 400, 350, 250, 140, 160, 140},
/* 5 */ { 100, 120, 550, 900, 700, 500, 400, 300, 220, 250, 160}
};
/* set wm_trigger knowing waitmask (param) and DEFevol (glob)*/
static void set_wm_trigger(CommonState *state)
{
unsigned int wm = state->wmidx;
if (wm > 10) {
wm = 10;
}
SPICE_VERIFY(DEFevol < 6);
state->wm_trigger = besttrigtab[DEFevol / 2][wm];
spice_assert(state->wm_trigger <= 2000);
spice_assert(state->wm_trigger >= 1);
}
static int ceil_log_2(int val) /* ceil(log_2(val)) */
{
int result;
//spice_assert(val>0);
if (val == 1) {
return 0;
}
result = 1;
val -= 1;
while (val >>= 1) {
result++;
}
return result;
}
/* number of leading zeroes in the byte, used by cntlzeroes(uint)*/
static const BYTE lzeroes[256] = {
8, 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};
/* count leading zeroes */
static unsigned int cnt_l_zeroes(const unsigned int bits)
{
if (bits & 0xff800000) {
return lzeroes[bits >> 24];
} else if (bits & 0xffff8000) {
return 8 + lzeroes[(bits >> 16) & 0x000000ff];
} else if (bits & 0xffffff80) {
return 16 + lzeroes[(bits >> 8) & 0x000000ff];
} else {
return 24 + lzeroes[bits & 0x000000ff];
}
}
#define QUIC_FAMILY_8BPC
#include "quic_family_tmpl.c"
#define QUIC_FAMILY_5BPC
#include "quic_family_tmpl.c"
static void decorrelate_init(QuicFamily *family, int bpc)
{
const unsigned int pixelbitmask = bppmask[bpc];
const unsigned int pixelbitmaskshr = pixelbitmask >> 1;
unsigned int s;
//spice_assert(bpc <= 8);
for (s = 0; s <= pixelbitmask; s++) {
if (s <= pixelbitmaskshr) {
family->xlatU2L[s] = s << 1;
} else {
family->xlatU2L[s] = ((pixelbitmask - s) << 1) + 1;
}
}
}
static void correlate_init(QuicFamily *family, int bpc)
{
const unsigned int pixelbitmask = bppmask[bpc];
unsigned long int s;
//spice_assert(bpc <= 8);
for (s = 0; s <= pixelbitmask; s++) {
if (s & 0x01) {
family->xlatL2U[s] = pixelbitmask - (s >> 1);
} else {
family->xlatL2U[s] = (s >> 1);
}
}
}
static void golomb_coding_slow(QuicFamily *family, const BYTE n, const unsigned int l,
unsigned int * const codeword,
unsigned int * const codewordlen)
{
if (n < family->nGRcodewords[l]) {
(*codeword) = bitat(l) | (n & bppmask[l]);
(*codewordlen) = (n >> l) + l + 1;
} else {
(*codeword) = n - family->nGRcodewords[l];
(*codewordlen) = family->notGRcwlen[l];
}
}
static void family_init(QuicFamily *family, int bpc, int limit)
{
int l, b;
for (l = 0; l < bpc; l++) { /* fill arrays indexed by code number */
int altprefixlen, altcodewords;
altprefixlen = limit - bpc;
if (altprefixlen > (int)(bppmask[bpc - l])) {
altprefixlen = bppmask[bpc - l];
}
altcodewords = bppmask[bpc] + 1 - (altprefixlen << l);
family->nGRcodewords[l] = (altprefixlen << l);
family->notGRsuffixlen[l] = ceil_log_2(altcodewords); /* needed for decoding only */
family->notGRcwlen[l] = altprefixlen + family->notGRsuffixlen[l];
family->notGRprefixmask[l] = bppmask[32 - altprefixlen]; /* needed for decoding only */
for (b = 0; b < 256; b++) {
unsigned int code, len;
golomb_coding_slow(family, b, l, &code, &len);
family->golomb_code[b][l] = code;
family->golomb_code_len[b][l] = len;
}
}
decorrelate_init(family, bpc);
correlate_init(family, bpc);
}
static void more_io_words(Encoder *encoder)
{
uint32_t *io_ptr;
int num_io_words = encoder->usr->more_space(encoder->usr, &io_ptr, encoder->rows_completed);
if (num_io_words <= 0) {
encoder->usr->error(encoder->usr, "%s: no more words\n", __FUNCTION__);
}
spice_assert(io_ptr);
encoder->io_words_count += num_io_words;
encoder->io_now = io_ptr;
encoder->io_end = encoder->io_now + num_io_words;
}
static inline void write_io_word(Encoder *encoder)
{
if (encoder->io_now == encoder->io_end) {
more_io_words(encoder);
}
*(encoder->io_now++) = GUINT32_TO_LE(encoder->io_word);
}
static inline void encode(Encoder *encoder, unsigned int word, unsigned int len)
{
int delta;
if (spice_extra_checks) {
spice_assert(len > 0 && len < 32);
spice_assert(!(word & ~bppmask[len]));
}
if ((delta = ((int)encoder->io_available_bits - len)) >= 0) {
encoder->io_available_bits = delta;
encoder->io_word |= word << encoder->io_available_bits;
return;
}
delta = -delta;
encoder->io_word |= word >> delta;
write_io_word(encoder);
encoder->io_available_bits = 32 - delta;
encoder->io_word = word << encoder->io_available_bits;
if (spice_extra_checks) {
spice_assert(encoder->io_available_bits < 32);
spice_assert((encoder->io_word & bppmask[encoder->io_available_bits]) == 0);
}
}
static inline void encode_32(Encoder *encoder, unsigned int word)
{
encode(encoder, word >> 16, 16);
encode(encoder, word & 0x0000ffff, 16);
}
static inline void flush(Encoder *encoder)
{
if (encoder->io_available_bits > 0 && encoder->io_available_bits != 32) {
encode(encoder, 0, encoder->io_available_bits);
}
encode_32(encoder, 0);
encode(encoder, 0, 1);
}
static inline void read_io_word(Encoder *encoder)
{
if (encoder->io_now == encoder->io_end) {
more_io_words(encoder);
}
if (spice_extra_checks) {
spice_assert(encoder->io_now < encoder->io_end);
}
encoder->io_next_word = GUINT32_FROM_LE(*(encoder->io_now++));
}
static inline void decode_eatbits(Encoder *encoder, int len)
{
int delta;
if (spice_extra_checks) {
spice_assert(len > 0 && len < 32);
}
encoder->io_word <<= len;
if ((delta = ((int)encoder->io_available_bits - len)) >= 0) {
encoder->io_available_bits = delta;
encoder->io_word |= encoder->io_next_word >> encoder->io_available_bits;
return;
}
delta = -delta;
encoder->io_word |= encoder->io_next_word << delta;
read_io_word(encoder);
encoder->io_available_bits = 32 - delta;
encoder->io_word |= (encoder->io_next_word >> encoder->io_available_bits);
}
static inline void decode_eat32bits(Encoder *encoder)
{
decode_eatbits(encoder, 16);
decode_eatbits(encoder, 16);
}
static inline void encode_ones(Encoder *encoder, unsigned int n)
{
unsigned int count;
for (count = n >> 5; count; count--) {
encode_32(encoder, ~0U);
}
if ((n &= 0x1f)) {
encode(encoder, (1U << n) - 1, n);
}
}
#define MELCSTATES 32 /* number of melcode states */
static const int J[MELCSTATES] = {
0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 5, 5, 6, 6, 7,
7, 8, 9, 10, 11, 12, 13, 14, 15
};
static void encoder_init_rle(CommonState *state)
{
state->melcstate = 0;
state->melclen = J[0];
state->melcorder = 1 << state->melclen;
}
static void encode_state_run(Encoder *encoder, CommonState *state, unsigned int runlen)
{
int hits = 0;
while (runlen >= state->melcorder) {
hits++;
runlen -= state->melcorder;
if (state->melcstate < MELCSTATES - 1) {
state->melclen = J[++state->melcstate];
state->melcorder = (1L << state->melclen);
}
}
/* send the required number of "hit" bits (one per occurrence
of a run of length melcorder). This number is never too big:
after 31 such "hit" bits, each "hit" would represent a run of 32K
pixels.
*/
encode_ones(encoder, hits);
encode(encoder, runlen, state->melclen + 1);
/* adjust melcoder parameters */
if (state->melcstate) {
state->melclen = J[--state->melcstate];
state->melcorder = (1L << state->melclen);
}
}
static void encode_run(Encoder *encoder, unsigned int runlen) //todo: try use end of line
{
encode_state_run(encoder, &encoder->rgb_state, runlen);
}
static void encode_channel_run(Encoder *encoder, Channel *channel, unsigned int runlen)
{
encode_state_run(encoder, &channel->state, runlen);
}
/* decoding routine: reads bits from the input and returns a run length. */
/* argument is the number of pixels left to end-of-line (bound on run length) */
static int decode_state_run(Encoder *encoder, CommonState *state)
{
int runlen = 0;
do {
register int temp, hits;
temp = lzeroes[(BYTE)(~(encoder->io_word >> 24))];/* number of leading ones in the
input stream, up to 8 */
for (hits = 1; hits <= temp; hits++) {
runlen += state->melcorder;
if (state->melcstate < MELCSTATES - 1) {
state->melclen = J[++state->melcstate];
state->melcorder = (1U << state->melclen);
}
}
if (temp != 8) {
decode_eatbits(encoder, temp + 1); /* consume the leading
0 of the remainder encoding */
break;
}
decode_eatbits(encoder, 8);
} while (1);
/* read the length of the remainder */
if (state->melclen) {
runlen += encoder->io_word >> (32 - state->melclen);
decode_eatbits(encoder, state->melclen);
}
/* adjust melcoder parameters */
if (state->melcstate) {
state->melclen = J[--state->melcstate];
state->melcorder = (1U << state->melclen);
}
return runlen;
}
static int decode_run(Encoder *encoder)
{
return decode_state_run(encoder, &encoder->rgb_state);
}
static int decode_channel_run(Encoder *encoder, Channel *channel)
{
return decode_state_run(encoder, &channel->state);
}
static inline void init_decode_io(Encoder *encoder)
{
encoder->io_next_word = encoder->io_word = GUINT32_FROM_LE(*(encoder->io_now++));
encoder->io_available_bits = 0;
}
#include <spice/start-packed.h>
typedef struct SPICE_ATTR_PACKED one_byte_pixel_t {
BYTE a;
} one_byte_t;
typedef struct SPICE_ATTR_PACKED four_bytes_pixel_t {
BYTE a;
BYTE b;
BYTE c;
BYTE d;
} four_bytes_t;
typedef struct SPICE_ATTR_PACKED rgb32_pixel_t {
BYTE b;
BYTE g;
BYTE r;
BYTE pad;
} rgb32_pixel_t;
typedef struct SPICE_ATTR_PACKED rgb24_pixel_t {
BYTE b;
BYTE g;
BYTE r;
} rgb24_pixel_t;
typedef uint16_t rgb16_pixel_t;
#include <spice/end-packed.h>
#define ONE_BYTE
#include "quic_tmpl.c"
#define FOUR_BYTE
#include "quic_tmpl.c"
#define QUIC_RGB32
#include "quic_rgb_tmpl.c"
#define QUIC_RGB24
#include "quic_rgb_tmpl.c"
#define QUIC_RGB16
#include "quic_rgb_tmpl.c"
#define QUIC_RGB16_TO_32
#include "quic_rgb_tmpl.c"
static void fill_model_structures(SPICE_GNUC_UNUSED Encoder *encoder, FamilyStat *family_stat,
unsigned int rep_first, unsigned int first_size,
unsigned int rep_next, unsigned int mul_size,
unsigned int levels, unsigned int ncounters,
unsigned int nbuckets, unsigned int n_buckets_ptrs)
{
unsigned int
bsize,
bstart,
bend = 0,
repcntr,
bnumber;
COUNTER * free_counter = family_stat->counters;/* first free location in the array of
counters */
bnumber = 0;
repcntr = rep_first + 1; /* first bucket */
bsize = first_size;
do { /* others */
if (bnumber) {
bstart = bend + 1;
} else {
bstart = 0;
}
if (!--repcntr) {
repcntr = rep_next;
bsize *= mul_size;
}
bend = bstart + bsize - 1;
if (bend + bsize >= levels) {
bend = levels - 1;
}
family_stat->buckets_buf[bnumber].pcounters = free_counter;
free_counter += ncounters;
spice_assert(bstart < n_buckets_ptrs);
{
unsigned int i;
spice_assert(bend < n_buckets_ptrs);
for (i = bstart; i <= bend; i++) {
family_stat->buckets_ptrs[i] = family_stat->buckets_buf + bnumber;
}
}
bnumber++;
} while (bend < levels - 1);
spice_assert(free_counter - family_stat->counters == nbuckets * ncounters);
}
static void find_model_params(Encoder *encoder,
const int bpc,
unsigned int *ncounters,
unsigned int *levels,
unsigned int *n_buckets_ptrs,
unsigned int *repfirst,
unsigned int *firstsize,
unsigned int *repnext,
unsigned int *mulsize,
unsigned int *nbuckets)
{
unsigned int bsize; /* bucket size */
unsigned int bstart, bend = 0; /* bucket start and end, range : 0 to levels-1*/
unsigned int repcntr; /* helper */
/* The only valid values are 1, 3 and 5.
0, 2 and 4 are obsolete and the rest of the
values are considered out of the range. */
SPICE_VERIFY(DEFevol == 1 || DEFevol == 3 || DEFevol == 5);
spice_assert(bpc <= 8 && bpc > 0);
*ncounters = 8;
*levels = 0x1 << bpc;
*n_buckets_ptrs = 0; /* ==0 means: not set yet */
switch (DEFevol) { /* set repfirst firstsize repnext mulsize */
case 1: /* buckets contain following numbers of contexts: 1 1 1 2 2 4 4 8 8 ... */
*repfirst = 3;
*firstsize = 1;
*repnext = 2;
*mulsize = 2;
break;
case 3: /* 1 2 4 8 16 32 64 ... */
*repfirst = 1;
*firstsize = 1;
*repnext = 1;
*mulsize = 2;
break;
case 5: /* 1 4 16 64 256 1024 4096 16384 65536 */
*repfirst = 1;
*firstsize = 1;
*repnext = 1;
*mulsize = 4;
break;
default:
encoder->usr->error(encoder->usr, "findmodelparams(): DEFevol out of range!!!\n");
return;
}
*nbuckets = 0;
repcntr = *repfirst + 1; /* first bucket */
bsize = *firstsize;
do { /* other buckets */
if (*nbuckets) { /* bucket start */
bstart = bend + 1;
} else {
bstart = 0;
}
if (!--repcntr) { /* bucket size */
repcntr = *repnext;
bsize *= *mulsize;
}
bend = bstart + bsize - 1; /* bucket end */
if (bend + bsize >= *levels) { /* if following bucked was bigger than current one */
bend = *levels - 1; /* concatenate them */
}
if (!*n_buckets_ptrs) { /* array size not set yet? */
*n_buckets_ptrs = *levels;
#if 0
if (bend == *levels - 1) { /* this bucket is last - all in the first array */
*n_buckets_ptrs = *levels;
} else if (bsize >= 256) { /* this bucket is allowed to reside in the 2nd table */
b_lo_ptrs = bstart;
spice_assert(bstart); /* previous bucket exists */
}
#endif
}
(*nbuckets)++;
} while (bend < *levels - 1);
}
static int init_model_structures(Encoder *encoder, FamilyStat *family_stat,
unsigned int rep_first, unsigned int first_size,
unsigned int rep_next, unsigned int mul_size,
unsigned int levels, unsigned int ncounters,
unsigned int n_buckets_ptrs, unsigned int n_buckets)
{
family_stat->buckets_ptrs = (s_bucket **)encoder->usr->malloc(encoder->usr,
n_buckets_ptrs *
sizeof(s_bucket *));
if (!family_stat->buckets_ptrs) {
return FALSE;
}
family_stat->counters = (COUNTER *)encoder->usr->malloc(encoder->usr,
n_buckets * sizeof(COUNTER) *
MAXNUMCODES);
if (!family_stat->counters) {
goto error_1;
}
family_stat->buckets_buf = (s_bucket *)encoder->usr->malloc(encoder->usr,
n_buckets * sizeof(s_bucket));
if (!family_stat->buckets_buf) {
goto error_2;
}
fill_model_structures(encoder, family_stat, rep_first, first_size, rep_next, mul_size, levels,
ncounters, n_buckets, n_buckets_ptrs);
return TRUE;
error_2:
encoder->usr->free(encoder->usr, family_stat->counters);
error_1:
encoder->usr->free(encoder->usr, family_stat->buckets_ptrs);
return FALSE;
}
static void free_family_stat(QuicUsrContext *usr, FamilyStat *family_stat)
{
usr->free(usr, family_stat->buckets_ptrs);
usr->free(usr, family_stat->counters);
usr->free(usr, family_stat->buckets_buf);
}
static int init_channel(Encoder *encoder, Channel *channel)
{
unsigned int ncounters;
unsigned int levels;
unsigned int rep_first;
unsigned int first_size;
unsigned int rep_next;
unsigned int mul_size;
unsigned int n_buckets;
unsigned int n_buckets_ptrs;
channel->correlate_row_width = 0;
channel->correlate_row = NULL;
find_model_params(encoder, 8, &ncounters, &levels, &n_buckets_ptrs, &rep_first,
&first_size, &rep_next, &mul_size, &n_buckets);
encoder->n_buckets_8bpc = n_buckets;
if (!init_model_structures(encoder, &channel->family_stat_8bpc, rep_first, first_size,
rep_next, mul_size, levels, ncounters, n_buckets_ptrs,
n_buckets)) {
return FALSE;
}
find_model_params(encoder, 5, &ncounters, &levels, &n_buckets_ptrs, &rep_first,
&first_size, &rep_next, &mul_size, &n_buckets);
encoder->n_buckets_5bpc = n_buckets;
if (!init_model_structures(encoder, &channel->family_stat_5bpc, rep_first, first_size,
rep_next, mul_size, levels, ncounters, n_buckets_ptrs,
n_buckets)) {
free_family_stat(encoder->usr, &channel->family_stat_8bpc);
return FALSE;
}
return TRUE;
}
static void destroy_channel(Encoder *encoder, Channel *channel)
{
QuicUsrContext *usr = encoder->usr;
if (channel->correlate_row) {
usr->free(usr, channel->correlate_row - 1);
}
free_family_stat(usr, &channel->family_stat_8bpc);
free_family_stat(usr, &channel->family_stat_5bpc);
}
static int init_encoder(Encoder *encoder, QuicUsrContext *usr)
{
int i;
encoder->usr = usr;
for (i = 0; i < MAX_CHANNELS; i++) {
if (!init_channel(encoder, &encoder->channels[i])) {
for (--i; i >= 0; i--) {
destroy_channel(encoder, &encoder->channels[i]);
}
return FALSE;
}
}
return TRUE;
}
static int encoder_reset(Encoder *encoder, uint32_t *io_ptr, uint32_t *io_ptr_end)
{
spice_assert(((uintptr_t)io_ptr % 4) == ((uintptr_t)io_ptr_end % 4));
spice_assert(io_ptr <= io_ptr_end);
encoder->rgb_state.waitcnt = 0;
encoder->rgb_state.tabrand_seed = stabrand();
encoder->rgb_state.wmidx = DEFwmistart;
encoder->rgb_state.wmileft = DEFwminext;
set_wm_trigger(&encoder->rgb_state);
encoder_init_rle(&encoder->rgb_state);
encoder->io_words_count = io_ptr_end - io_ptr;
encoder->io_now = io_ptr;
encoder->io_end = io_ptr_end;
encoder->rows_completed = 0;
return TRUE;
}
static int encoder_reset_channels(Encoder *encoder, int channels, int width, int bpc)
{
int i;
for (i = 0; i < channels; i++) {
s_bucket *bucket;
s_bucket *end_bucket;
if (encoder->channels[i].correlate_row_width < width) {
encoder->channels[i].correlate_row_width = 0;
if (encoder->channels[i].correlate_row) {
encoder->usr->free(encoder->usr, encoder->channels[i].correlate_row - 1);
}
if (!(encoder->channels[i].correlate_row = (BYTE *)encoder->usr->malloc(encoder->usr,
width + 1))) {
return FALSE;
}
encoder->channels[i].correlate_row++;
encoder->channels[i].correlate_row_width = width;
}
if (bpc == 8) {
MEMCLEAR(encoder->channels[i].family_stat_8bpc.counters,
encoder->n_buckets_8bpc * sizeof(COUNTER) * MAXNUMCODES);
bucket = encoder->channels[i].family_stat_8bpc.buckets_buf;
end_bucket = bucket + encoder->n_buckets_8bpc;
for (; bucket < end_bucket; bucket++) {
bucket->bestcode = /*BPC*/ 8 - 1;
}
encoder->channels[i]._buckets_ptrs = encoder->channels[i].family_stat_8bpc.buckets_ptrs;
} else if (bpc == 5) {
MEMCLEAR(encoder->channels[i].family_stat_5bpc.counters,
encoder->n_buckets_5bpc * sizeof(COUNTER) * MAXNUMCODES);
bucket = encoder->channels[i].family_stat_5bpc.buckets_buf;
end_bucket = bucket + encoder->n_buckets_5bpc;
for (; bucket < end_bucket; bucket++) {
bucket->bestcode = /*BPC*/ 5 - 1;
}
encoder->channels[i]._buckets_ptrs = encoder->channels[i].family_stat_5bpc.buckets_ptrs;
} else {
encoder->usr->warn(encoder->usr, "%s: bad bpc %d\n", __FUNCTION__, bpc);
return FALSE;
}
encoder->channels[i].state.waitcnt = 0;
encoder->channels[i].state.tabrand_seed = stabrand();
encoder->channels[i].state.wmidx = DEFwmistart;
encoder->channels[i].state.wmileft = DEFwminext;
set_wm_trigger(&encoder->channels[i].state);
encoder_init_rle(&encoder->channels[i].state);
}
return TRUE;
}
static void quic_image_params(Encoder *encoder, QuicImageType type, int *channels, int *bpc)
{
spice_assert(channels && bpc);
switch (type) {
case QUIC_IMAGE_TYPE_GRAY:
*channels = 1;
*bpc = 8;
break;
case QUIC_IMAGE_TYPE_RGB16:
*channels = 3;
*bpc = 5;
break;
case QUIC_IMAGE_TYPE_RGB24:
*channels = 3;
*bpc = 8;
break;
case QUIC_IMAGE_TYPE_RGB32:
*channels = 3;
*bpc = 8;
break;
case QUIC_IMAGE_TYPE_RGBA:
*channels = 4;
*bpc = 8;
break;
case QUIC_IMAGE_TYPE_INVALID:
default:
*channels = 0;
*bpc = 0;
encoder->usr->error(encoder->usr, "bad image type\n");
}
}
#define FILL_LINES() { \
if (line == lines_end) { \
int n = encoder->usr->more_lines(encoder->usr, &line); \
if (n <= 0 || line == NULL) { \
encoder->usr->error(encoder->usr, "more lines failed\n"); \
} \
lines_end = line + n * stride; \
} \
}
#define NEXT_LINE() { \
line += stride; \
FILL_LINES(); \
}
#define QUIC_COMPRESS_RGB(bits) \
encoder->channels[0].correlate_row[-1] = 0; \
encoder->channels[1].correlate_row[-1] = 0; \
encoder->channels[2].correlate_row[-1] = 0; \
quic_rgb##bits##_compress_row0(encoder, (rgb##bits##_pixel_t *)(line), width); \
encoder->rows_completed++; \
for (row = 1; row < height; row++) { \
prev = line; \
NEXT_LINE(); \
encoder->channels[0].correlate_row[-1] = encoder->channels[0].correlate_row[0]; \
encoder->channels[1].correlate_row[-1] = encoder->channels[1].correlate_row[0]; \
encoder->channels[2].correlate_row[-1] = encoder->channels[2].correlate_row[0]; \
quic_rgb##bits##_compress_row(encoder, (rgb##bits##_pixel_t *)prev, \
(rgb##bits##_pixel_t *)line, width); \
encoder->rows_completed++; \
}
int quic_encode(QuicContext *quic, QuicImageType type, int width, int height,
uint8_t *line, unsigned int num_lines, int stride,
uint32_t *io_ptr, unsigned int num_io_words)
{
Encoder *encoder = (Encoder *)quic;
uint32_t *io_ptr_end = io_ptr + num_io_words;
uint8_t *lines_end;
int row;
uint8_t *prev;
int channels;
int bpc;
lines_end = line + num_lines * stride;
if (line == NULL && lines_end != line) {
spice_warn_if_reached();
return QUIC_ERROR;
}
quic_image_params(encoder, type, &channels, &bpc);
if (!encoder_reset(encoder, io_ptr, io_ptr_end) ||
!encoder_reset_channels(encoder, channels, width, bpc)) {
return QUIC_ERROR;
}
encoder->io_word = 0;
encoder->io_available_bits = 32;
encode_32(encoder, QUIC_MAGIC);
encode_32(encoder, QUIC_VERSION);
encode_32(encoder, type);
encode_32(encoder, width);
encode_32(encoder, height);
FILL_LINES();
switch (type) {
case QUIC_IMAGE_TYPE_RGB32:
spice_assert(ABS(stride) >= width * 4);
QUIC_COMPRESS_RGB(32);
break;
case QUIC_IMAGE_TYPE_RGB24:
spice_assert(ABS(stride) >= width * 3);
QUIC_COMPRESS_RGB(24);
break;
case QUIC_IMAGE_TYPE_RGB16:
spice_assert(ABS(stride) >= width * 2);
QUIC_COMPRESS_RGB(16);
break;
case QUIC_IMAGE_TYPE_RGBA:
spice_assert(ABS(stride) >= width * 4);
encoder->channels[0].correlate_row[-1] = 0;
encoder->channels[1].correlate_row[-1] = 0;
encoder->channels[2].correlate_row[-1] = 0;
quic_rgb32_compress_row0(encoder, (rgb32_pixel_t *)(line), width);
encoder->channels[3].correlate_row[-1] = 0;
quic_four_compress_row0(encoder, &encoder->channels[3], (four_bytes_t *)(line + 3), width);
encoder->rows_completed++;
for (row = 1; row < height; row++) {
prev = line;
NEXT_LINE();
encoder->channels[0].correlate_row[-1] = encoder->channels[0].correlate_row[0];
encoder->channels[1].correlate_row[-1] = encoder->channels[1].correlate_row[0];
encoder->channels[2].correlate_row[-1] = encoder->channels[2].correlate_row[0];
quic_rgb32_compress_row(encoder, (rgb32_pixel_t *)prev, (rgb32_pixel_t *)line, width);
encoder->channels[3].correlate_row[-1] = encoder->channels[3].correlate_row[0];
quic_four_compress_row(encoder, &encoder->channels[3], (four_bytes_t *)(prev + 3),
(four_bytes_t *)(line + 3), width);
encoder->rows_completed++;
}
break;
case QUIC_IMAGE_TYPE_GRAY:
spice_assert(ABS(stride) >= width);
encoder->channels[0].correlate_row[-1] = 0;
quic_one_compress_row0(encoder, &encoder->channels[0], (one_byte_t *)line, width);
encoder->rows_completed++;
for (row = 1; row < height; row++) {
prev = line;
NEXT_LINE();
encoder->channels[0].correlate_row[-1] = encoder->channels[0].correlate_row[0];
quic_one_compress_row(encoder, &encoder->channels[0], (one_byte_t *)prev,
(one_byte_t *)line, width);
encoder->rows_completed++;
}
break;
case QUIC_IMAGE_TYPE_INVALID:
default:
encoder->usr->error(encoder->usr, "bad image type\n");
}
flush(encoder);
encoder->io_words_count -= (encoder->io_end - encoder->io_now);
return encoder->io_words_count;
}
int quic_decode_begin(QuicContext *quic, uint32_t *io_ptr, unsigned int num_io_words,
QuicImageType *out_type, int *out_width, int *out_height)
{
Encoder *encoder = (Encoder *)quic;
uint32_t *io_ptr_end = io_ptr + num_io_words;
QuicImageType type;
int width;
int height;
uint32_t magic;
uint32_t version;
int channels;
int bpc;
if (!encoder_reset(encoder, io_ptr, io_ptr_end)) {
return QUIC_ERROR;
}
init_decode_io(encoder);
magic = encoder->io_word;
decode_eat32bits(encoder);
if (magic != QUIC_MAGIC) {
encoder->usr->warn(encoder->usr, "bad magic\n");
return QUIC_ERROR;
}
version = encoder->io_word;
decode_eat32bits(encoder);
if (version != QUIC_VERSION) {
encoder->usr->warn(encoder->usr, "bad version\n");
return QUIC_ERROR;
}
type = (QuicImageType)encoder->io_word;
decode_eat32bits(encoder);
width = encoder->io_word;
decode_eat32bits(encoder);
height = encoder->io_word;
decode_eat32bits(encoder);
quic_image_params(encoder, type, &channels, &bpc);
if (!encoder_reset_channels(encoder, channels, width, bpc)) {
return QUIC_ERROR;
}
*out_width = encoder->width = width;
*out_height = encoder->height = height;
*out_type = encoder->type = type;
return QUIC_OK;
}
static void uncompress_rgba(Encoder *encoder, uint8_t *buf, int stride)
{
unsigned int row;
uint8_t *prev;
encoder->channels[0].correlate_row[-1] = 0;
encoder->channels[1].correlate_row[-1] = 0;
encoder->channels[2].correlate_row[-1] = 0;
quic_rgb32_uncompress_row0(encoder, (rgb32_pixel_t *)buf, encoder->width);
encoder->channels[3].correlate_row[-1] = 0;
quic_four_uncompress_row0(encoder, &encoder->channels[3], (four_bytes_t *)(buf + 3),
encoder->width);
encoder->rows_completed++;
for (row = 1; row < encoder->height; row++) {
prev = buf;
buf += stride;
encoder->channels[0].correlate_row[-1] = encoder->channels[0].correlate_row[0];
encoder->channels[1].correlate_row[-1] = encoder->channels[1].correlate_row[0];
encoder->channels[2].correlate_row[-1] = encoder->channels[2].correlate_row[0];
quic_rgb32_uncompress_row(encoder, (rgb32_pixel_t *)prev, (rgb32_pixel_t *)buf,
encoder->width);
encoder->channels[3].correlate_row[-1] = encoder->channels[3].correlate_row[0];
quic_four_uncompress_row(encoder, &encoder->channels[3], (four_bytes_t *)(prev + 3),
(four_bytes_t *)(buf + 3), encoder->width);
encoder->rows_completed++;
}
}
static void uncompress_gray(Encoder *encoder, uint8_t *buf, int stride)
{
unsigned int row;
uint8_t *prev;
encoder->channels[0].correlate_row[-1] = 0;
quic_one_uncompress_row0(encoder, &encoder->channels[0], (one_byte_t *)buf, encoder->width);
encoder->rows_completed++;
for (row = 1; row < encoder->height; row++) {
prev = buf;
buf += stride;
encoder->channels[0].correlate_row[-1] = encoder->channels[0].correlate_row[0];
quic_one_uncompress_row(encoder, &encoder->channels[0], (one_byte_t *)prev,
(one_byte_t *)buf, encoder->width);
encoder->rows_completed++;
}
}
#define QUIC_UNCOMPRESS_RGB(prefix, type) \
encoder->channels[0].correlate_row[-1] = 0; \
encoder->channels[1].correlate_row[-1] = 0; \
encoder->channels[2].correlate_row[-1] = 0; \
quic_rgb##prefix##_uncompress_row0(encoder, (type *)buf, encoder->width); \
encoder->rows_completed++; \
for (row = 1; row < encoder->height; row++) { \
prev = buf; \
buf += stride; \
encoder->channels[0].correlate_row[-1] = encoder->channels[0].correlate_row[0]; \
encoder->channels[1].correlate_row[-1] = encoder->channels[1].correlate_row[0]; \
encoder->channels[2].correlate_row[-1] = encoder->channels[2].correlate_row[0]; \
quic_rgb##prefix##_uncompress_row(encoder, (type *)prev, (type *)buf, \
encoder->width); \
encoder->rows_completed++; \
}
int quic_decode(QuicContext *quic, QuicImageType type, uint8_t *buf, int stride)
{
Encoder *encoder = (Encoder *)quic;
unsigned int row;
uint8_t *prev;
spice_assert(buf);
switch (encoder->type) {
case QUIC_IMAGE_TYPE_RGB32:
case QUIC_IMAGE_TYPE_RGB24:
if (type == QUIC_IMAGE_TYPE_RGB32) {
spice_assert(ABS(stride) >= (int)encoder->width * 4);
QUIC_UNCOMPRESS_RGB(32, rgb32_pixel_t);
break;
} else if (type == QUIC_IMAGE_TYPE_RGB24) {
spice_assert(ABS(stride) >= (int)encoder->width * 3);
QUIC_UNCOMPRESS_RGB(24, rgb24_pixel_t);
break;
}
encoder->usr->warn(encoder->usr, "unsupported output format\n");
return QUIC_ERROR;
case QUIC_IMAGE_TYPE_RGB16:
if (type == QUIC_IMAGE_TYPE_RGB16) {
spice_assert(ABS(stride) >= (int)encoder->width * 2);
QUIC_UNCOMPRESS_RGB(16, rgb16_pixel_t);
} else if (type == QUIC_IMAGE_TYPE_RGB32) {
spice_assert(ABS(stride) >= (int)encoder->width * 4);
QUIC_UNCOMPRESS_RGB(16_to_32, rgb32_pixel_t);
} else {
encoder->usr->warn(encoder->usr, "unsupported output format\n");
return QUIC_ERROR;
}
break;
case QUIC_IMAGE_TYPE_RGBA:
if (type != QUIC_IMAGE_TYPE_RGBA) {
encoder->usr->warn(encoder->usr, "unsupported output format\n");
return QUIC_ERROR;
}
spice_assert(ABS(stride) >= (int)encoder->width * 4);
uncompress_rgba(encoder, buf, stride);
break;
case QUIC_IMAGE_TYPE_GRAY:
if (type != QUIC_IMAGE_TYPE_GRAY) {
encoder->usr->warn(encoder->usr, "unsupported output format\n");
return QUIC_ERROR;
}
spice_assert(ABS(stride) >= (int)encoder->width);
uncompress_gray(encoder, buf, stride);
break;
case QUIC_IMAGE_TYPE_INVALID:
default:
encoder->usr->error(encoder->usr, "bad image type\n");
}
return QUIC_OK;
}
QuicContext *quic_create(QuicUsrContext *usr)
{
Encoder *encoder;
if (!usr || !usr->error || !usr->warn || !usr->info || !usr->malloc ||
!usr->free || !usr->more_space || !usr->more_lines) {
return NULL;
}
if (!(encoder = (Encoder *)usr->malloc(usr, sizeof(Encoder)))) {
return NULL;
}
if (!init_encoder(encoder, usr)) {
usr->free(usr, encoder);
return NULL;
}
return (QuicContext *)encoder;
}
void quic_destroy(QuicContext *quic)
{
Encoder *encoder = (Encoder *)quic;
int i;
if (!quic) {
return;
}
for (i = 0; i < MAX_CHANNELS; i++) {
destroy_channel(encoder, &encoder->channels[i]);
}
encoder->usr->free(encoder->usr, encoder);
}
SPICE_CONSTRUCTOR_FUNC(quic_global_init)
{
family_init(&family_8bpc, 8, DEFmaxclen);
family_init(&family_5bpc, 5, DEFmaxclen);
}