Add LZ4 compression support.

- Add lz4 encoder to compress an image of type LZ4 (see spice_common).
- Add code in red_worker to use LZ4 when it is enabled, and the client
  supports it through its display capability, or fallback to LZ.
- Add enable_lz4 switch in the configure script. Show LZ4 support at the
  end.
This commit is contained in:
Javier Celaya 2014-11-13 17:00:58 +01:00 committed by Christophe Fergeau
parent 84b3a5079d
commit b532ef0866
7 changed files with 359 additions and 7 deletions

View File

@ -138,6 +138,12 @@ AC_ARG_ENABLE(opengl,
AS_IF([test x"$enable_opengl" != "xno"], [enable_opengl="yes"])
AM_CONDITIONAL(SUPPORT_GL, test "x$enable_opengl" = "xyes")
AC_ARG_ENABLE(lz4,
[ --enable-lz4 Enable lz4 compression algorithm],,
[enable_lz4="no"])
AS_IF([test x"$enable_lz4" != "xno"], [enable_lz4="yes"])
AM_CONDITIONAL(SUPPORT_LZ4, test "x$enable_lz4" = "xyes")
AC_ARG_ENABLE(smartcard,
[ --enable-smartcard Enable network redirection],,
[enable_smartcard="no"])
@ -287,6 +293,13 @@ AC_ARG_ENABLE([xinerama],
[AS_HELP_STRING([--disable-xinerama],
[disable Xinerama library @<:@default=no@:>@])])
if test "x$enable_lz4" = "xyes"; then
PKG_CHECK_MODULES(LZ4, liblz4)
AC_DEFINE([USE_LZ4], [1], [Define to build with Lz4 support])
fi
AC_SUBST(LZ4_CFLAGS)
AC_SUBST(LZ4_LIBS)
if test "x$red_target" = "xx11" && test "x$enable_client" = "xyes" ; then
if test "$os_linux" = yes; then
PKG_CHECK_MODULES(ALSA, alsa)
@ -549,6 +562,8 @@ echo "
GUI: ${enable_gui}
" ; fi ; echo "\
LZ4 support: ${enable_lz4}
Smartcard: ${enable_smartcard}
SASL support: ${enable_sasl}

View File

@ -6,6 +6,7 @@ AM_CPPFLAGS = \
-DRED_STATISTICS \
$(COMMON_CFLAGS) \
$(GLIB2_CFLAGS) \
$(LZ4_CFLAGS) \
$(PIXMAN_CFLAGS) \
$(SASL_CFLAGS) \
$(SLIRP_CFLAGS) \
@ -35,6 +36,7 @@ libspice_server_la_LIBADD = \
$(GL_LIBS) \
$(GLIB2_LIBS) \
$(JPEG_LIBS) \
$(LZ4_LIBS) \
$(LIBRT) \
$(PIXMAN_LIBS) \
$(SASL_LIBS) \
@ -75,6 +77,8 @@ libspice_server_la_SOURCES = \
inputs_channel.h \
jpeg_encoder.c \
jpeg_encoder.h \
lz4_encoder.c \
lz4_encoder.h \
main_channel.c \
main_channel.h \
mjpeg_encoder.c \

122
server/lz4_encoder.c Normal file
View File

@ -0,0 +1,122 @@
/*
Copyright (C) 2014 Flexible Software Solutions S.L.
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/>.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#ifdef USE_LZ4
#define SPICE_LOG_DOMAIN "SpiceLz4Encoder"
#include <arpa/inet.h>
#include <lz4.h>
#include "red_common.h"
#include "lz4_encoder.h"
typedef struct Lz4Encoder {
Lz4EncoderUsrContext *usr;
} Lz4Encoder;
Lz4EncoderContext* lz4_encoder_create(Lz4EncoderUsrContext *usr)
{
Lz4Encoder *enc;
if (!usr->more_space || !usr->more_lines) {
return NULL;
}
enc = spice_new0(Lz4Encoder, 1);
enc->usr = usr;
return (Lz4EncoderContext*)enc;
}
void lz4_encoder_destroy(Lz4EncoderContext* encoder)
{
free(encoder);
}
int lz4_encode(Lz4EncoderContext *lz4, int height, int stride,
uint8_t *io_ptr, unsigned int num_io_bytes)
{
Lz4Encoder *enc = (Lz4Encoder *)lz4;
uint8_t *lines;
int num_lines = 0;
int total_lines = 0;
int in_size, enc_size, out_size = 0, already_copied;
int stride_abs = abs(stride);
uint8_t *in_buf, *compressed_lines;
uint8_t *out_buf = io_ptr;
LZ4_stream_t *stream = LZ4_createStream();
// Encode direction
*(out_buf++) = stride < 0 ? 1 : 0;
num_io_bytes--;
do {
num_lines = enc->usr->more_lines(enc->usr, &lines);
if (num_lines <= 0) {
spice_error("more lines failed");
LZ4_freeStream(stream);
return 0;
}
in_buf = stride < 0 ? lines - (stride_abs * (num_lines - 1)) : lines;
lines += stride * num_lines;
in_size = stride_abs * num_lines;
compressed_lines = (uint8_t *) malloc(LZ4_compressBound(in_size) + 4);
enc_size = LZ4_compress_continue(stream, (const char *) in_buf,
(char *) compressed_lines + 4, in_size);
if (enc_size <= 0) {
spice_error("compress failed!");
free(compressed_lines);
LZ4_freeStream(stream);
return 0;
}
*((uint32_t *)compressed_lines) = htonl(enc_size);
out_size += enc_size += 4;
already_copied = 0;
while (num_io_bytes < enc_size) {
memcpy(out_buf, compressed_lines + already_copied, num_io_bytes);
already_copied += num_io_bytes;
enc_size -= num_io_bytes;
num_io_bytes = enc->usr->more_space(enc->usr, &io_ptr);
if (num_io_bytes <= 0) {
spice_error("more space failed");
free(compressed_lines);
LZ4_freeStream(stream);
return 0;
}
out_buf = io_ptr;
}
memcpy(out_buf, compressed_lines + already_copied, enc_size);
out_buf += enc_size;
num_io_bytes -= enc_size;
free(compressed_lines);
total_lines += num_lines;
} while (total_lines < height);
LZ4_freeStream(stream);
if (total_lines != height) {
spice_error("too many lines\n");
out_size = 0;
}
return out_size;
}
#endif // USE_LZ4

50
server/lz4_encoder.h Normal file
View File

@ -0,0 +1,50 @@
/*
Copyright (C) 2014 Flexible Software Solutions S.L.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the
distribution.
* Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef _H_LZ4_ENCODER
#define _H_LZ4_ENCODER
#include <spice/types.h>
typedef void* Lz4EncoderContext;
typedef struct Lz4EncoderUsrContext Lz4EncoderUsrContext;
struct Lz4EncoderUsrContext {
int (*more_space)(Lz4EncoderUsrContext *usr, uint8_t **io_ptr);
int (*more_lines)(Lz4EncoderUsrContext *usr, uint8_t **lines);
};
Lz4EncoderContext* lz4_encoder_create(Lz4EncoderUsrContext *usr);
void lz4_encoder_destroy(Lz4EncoderContext *encoder);
/* returns the total size of the encoded data. Images must be supplied from the
top line to the bottom */
int lz4_encode(Lz4EncoderContext *lz4, int height, int stride,
uint8_t *io_ptr, unsigned int num_io_bytes);
#endif

View File

@ -74,6 +74,9 @@
#include "red_memslots.h"
#include "red_parse_qxl.h"
#include "jpeg_encoder.h"
#ifdef USE_LZ4
#include "lz4_encoder.h"
#endif
#include "demarshallers.h"
#include "zlib_encoder.h"
#include "red_channel.h"
@ -237,6 +240,7 @@ static const char *quic_stat_name = "quic";
static const char *jpeg_stat_name = "jpeg";
static const char *zlib_stat_name = "zlib_glz";
static const char *jpeg_alpha_stat_name = "jpeg_alpha";
static const char *lz4_stat_name = "lz4";
static inline void stat_compress_init(stat_info_t *info, const char *name)
{
@ -599,6 +603,13 @@ typedef struct {
EncoderData data;
} JpegData;
#ifdef USE_LZ4
typedef struct {
Lz4EncoderUsrContext usr;
EncoderData data;
} Lz4Data;
#endif
typedef struct {
ZlibEncoderUsrContext usr;
EncoderData data;
@ -740,6 +751,7 @@ struct DisplayChannel {
stat_info_t jpeg_stat;
stat_info_t zlib_glz_stat;
stat_info_t jpeg_alpha_stat;
stat_info_t lz4_stat;
#endif
};
@ -998,6 +1010,11 @@ typedef struct RedWorker {
JpegData jpeg_data;
JpegEncoderContext *jpeg;
#ifdef USE_LZ4
Lz4Data lz4_data;
Lz4EncoderContext *lz4;
#endif
ZlibData zlib_data;
ZlibEncoder *zlib;
@ -1190,27 +1207,37 @@ static void print_compress_stats(DisplayChannel *display_channel)
stat_byte_to_mega(display_channel->jpeg_alpha_stat.comp_size),
stat_cpu_time_to_sec(display_channel->jpeg_alpha_stat.total)
);
spice_info("LZ4 \t%8d\t%13.2f\t%12.2f\t%12.2f",
display_channel->lz4_stat.count,
stat_byte_to_mega(display_channel->lz4_stat.orig_size),
stat_byte_to_mega(display_channel->lz4_stat.comp_size),
stat_cpu_time_to_sec(display_channel->lz4_stat.total)
);
spice_info("-------------------------------------------------------------------");
spice_info("Total \t%8d\t%13.2f\t%12.2f\t%12.2f",
display_channel->lz_stat.count + display_channel->glz_stat.count +
display_channel->quic_stat.count +
display_channel->jpeg_stat.count +
display_channel->lz4_stat.count +
display_channel->jpeg_alpha_stat.count,
stat_byte_to_mega(display_channel->lz_stat.orig_size +
display_channel->glz_stat.orig_size +
display_channel->quic_stat.orig_size +
display_channel->jpeg_stat.orig_size +
display_channel->lz4_stat.orig_size +
display_channel->jpeg_alpha_stat.orig_size),
stat_byte_to_mega(display_channel->lz_stat.comp_size +
glz_enc_size +
display_channel->quic_stat.comp_size +
display_channel->jpeg_stat.comp_size +
display_channel->lz4_stat.comp_size +
display_channel->jpeg_alpha_stat.comp_size),
stat_cpu_time_to_sec(display_channel->lz_stat.total +
display_channel->glz_stat.total +
display_channel->zlib_glz_stat.total +
display_channel->quic_stat.total +
display_channel->jpeg_stat.total +
display_channel->lz4_stat.total +
display_channel->jpeg_alpha_stat.total)
);
}
@ -5725,6 +5752,14 @@ static int jpeg_usr_more_space(JpegEncoderUsrContext *usr, uint8_t **io_ptr)
return (encoder_usr_more_space(usr_data, (uint32_t **)io_ptr) << 2);
}
#ifdef USE_LZ4
static int lz4_usr_more_space(Lz4EncoderUsrContext *usr, uint8_t **io_ptr)
{
EncoderData *usr_data = &(((Lz4Data *)usr)->data);
return (encoder_usr_more_space(usr_data, (uint32_t **)io_ptr) << 2);
}
#endif
static int zlib_usr_more_space(ZlibEncoderUsrContext *usr, uint8_t **io_ptr)
{
EncoderData *usr_data = &(((ZlibData *)usr)->data);
@ -5785,6 +5820,14 @@ static int jpeg_usr_more_lines(JpegEncoderUsrContext *usr, uint8_t **lines)
return encoder_usr_more_lines(usr_data, lines);
}
#ifdef USE_LZ4
static int lz4_usr_more_lines(Lz4EncoderUsrContext *usr, uint8_t **lines)
{
EncoderData *usr_data = &(((Lz4Data *)usr)->data);
return encoder_usr_more_lines(usr_data, lines);
}
#endif
static int zlib_usr_more_input(ZlibEncoderUsrContext *usr, uint8_t** input)
{
EncoderData *usr_data = &(((ZlibData *)usr)->data);
@ -5886,6 +5929,20 @@ static inline void red_init_jpeg(RedWorker *worker)
}
}
#ifdef USE_LZ4
static inline void red_init_lz4(RedWorker *worker)
{
worker->lz4_data.usr.more_space = lz4_usr_more_space;
worker->lz4_data.usr.more_lines = lz4_usr_more_lines;
worker->lz4 = lz4_encoder_create(&worker->lz4_data.usr);
if (!worker->lz4) {
spice_critical("create lz4 encoder failed");
}
}
#endif
static inline void red_init_zlib(RedWorker *worker)
{
worker->zlib_data.usr.more_space = zlib_usr_more_space;
@ -6355,6 +6412,80 @@ static int red_jpeg_compress_image(DisplayChannelClient *dcc, SpiceImage *dest,
return TRUE;
}
#ifdef USE_LZ4
static int red_lz4_compress_image(DisplayChannelClient *dcc, SpiceImage *dest,
SpiceBitmap *src, compress_send_data_t* o_comp_data,
uint32_t group_id)
{
DisplayChannel *display_channel = DCC_TO_DC(dcc);
RedWorker *worker = display_channel->common.worker;
Lz4Data *lz4_data = &worker->lz4_data;
Lz4EncoderContext *lz4 = worker->lz4;
int lz4_size = 0;
int stride;
#ifdef COMPRESS_STAT
stat_time_t start_time = stat_now();
#endif
lz4_data->data.bufs_tail = red_display_alloc_compress_buf(dcc);
lz4_data->data.bufs_head = lz4_data->data.bufs_tail;
if (!lz4_data->data.bufs_head) {
spice_warning("failed to allocate compress buffer");
return FALSE;
}
lz4_data->data.bufs_head->send_next = NULL;
lz4_data->data.dcc = dcc;
if (setjmp(lz4_data->data.jmp_env)) {
while (lz4_data->data.bufs_head) {
RedCompressBuf *buf = lz4_data->data.bufs_head;
lz4_data->data.bufs_head = buf->send_next;
red_display_free_compress_buf(dcc, buf);
}
return FALSE;
}
if (src->data->flags & SPICE_CHUNKS_FLAGS_UNSTABLE) {
spice_chunks_linearize(src->data);
}
lz4_data->data.u.lines_data.chunks = src->data;
lz4_data->data.u.lines_data.stride = src->stride;
lz4_data->usr.more_lines = lz4_usr_more_lines;
if ((src->flags & SPICE_BITMAP_FLAGS_TOP_DOWN)) {
lz4_data->data.u.lines_data.next = 0;
lz4_data->data.u.lines_data.reverse = 0;
stride = src->stride;
} else {
lz4_data->data.u.lines_data.next = src->data->num_chunks - 1;
lz4_data->data.u.lines_data.reverse = 1;
stride = -src->stride;
}
lz4_size = lz4_encode(lz4, src->y, stride, (uint8_t*)lz4_data->data.bufs_head->buf,
sizeof(lz4_data->data.bufs_head->buf));
// the compressed buffer is bigger than the original data
if (lz4_size > (src->y * src->stride)) {
longjmp(lz4_data->data.jmp_env, 1);
}
dest->descriptor.type = SPICE_IMAGE_TYPE_LZ4;
dest->u.lz4.data_size = lz4_size;
o_comp_data->comp_buf = lz4_data->data.bufs_head;
o_comp_data->comp_buf_size = lz4_size;
stat_compress_add(&display_channel->lz4_stat, start_time, src->stride * src->y,
o_comp_data->comp_buf_size);
return TRUE;
}
#endif
static inline int red_quic_compress_image(DisplayChannelClient *dcc, SpiceImage *dest,
SpiceBitmap *src, compress_send_data_t* o_comp_data,
uint32_t group_id)
@ -6471,6 +6602,7 @@ static inline int red_compress_image(DisplayChannelClient *dcc,
if (_stride_is_extra(src) || (src->data->flags & SPICE_CHUNKS_FLAGS_UNSTABLE)) {
if ((image_compression == SPICE_IMAGE_COMPRESS_LZ) ||
(image_compression == SPICE_IMAGE_COMPRESS_GLZ) ||
(image_compression == SPICE_IMAGE_COMPRESS_LZ4) ||
BITMAP_FMT_IS_PLT[src->format]) {
return FALSE;
} else {
@ -6522,7 +6654,8 @@ static inline int red_compress_image(DisplayChannelClient *dcc,
(src->x * src->y) < glz_enc_dictionary_get_size(
dcc->glz_dict->dict));
} else if ((image_compression == SPICE_IMAGE_COMPRESS_AUTO_LZ) ||
(image_compression == SPICE_IMAGE_COMPRESS_LZ)) {
(image_compression == SPICE_IMAGE_COMPRESS_LZ) ||
(image_compression == SPICE_IMAGE_COMPRESS_LZ4)) {
glz = FALSE;
} else {
spice_error("invalid image compression type %u", image_compression);
@ -6543,8 +6676,16 @@ static inline int red_compress_image(DisplayChannelClient *dcc,
}
if (!glz) {
ret = red_lz_compress_image(dcc, dest, src, o_comp_data,
drawable->group_id);
#ifdef USE_LZ4
if (image_compression == SPICE_IMAGE_COMPRESS_LZ4 &&
red_channel_client_test_remote_cap(&dcc->common.base,
SPICE_DISPLAY_CAP_LZ4_COMPRESSION)) {
ret = red_lz4_compress_image(dcc, dest, src, o_comp_data,
drawable->group_id);
} else
#endif
ret = red_lz_compress_image(dcc, dest, src, o_comp_data,
drawable->group_id);
#ifdef COMPRESS_DEBUG
spice_info("LZ LOCAL compress");
#endif
@ -8775,9 +8916,18 @@ static void red_marshall_image(RedChannelClient *rcc, SpiceMarshaller *m, ImageI
&comp_send_data,
worker->mem_slots.internal_groupslot_id);
} else {
comp_succeeded = red_lz_compress_image(dcc, &red_image, &bitmap,
&comp_send_data,
worker->mem_slots.internal_groupslot_id);
#ifdef USE_LZ4
if (comp_mode == SPICE_IMAGE_COMPRESS_LZ4 &&
red_channel_client_test_remote_cap(&dcc->common.base,
SPICE_DISPLAY_CAP_LZ4_COMPRESSION)) {
comp_succeeded = red_lz4_compress_image(dcc, &red_image, &bitmap,
&comp_send_data,
worker->mem_slots.internal_groupslot_id);
} else
#endif
comp_succeeded = red_lz_compress_image(dcc, &red_image, &bitmap,
&comp_send_data,
worker->mem_slots.internal_groupslot_id);
}
}
@ -10567,6 +10717,7 @@ static void display_channel_create(RedWorker *worker, int migrate)
stat_compress_init(&display_channel->jpeg_stat, jpeg_stat_name);
stat_compress_init(&display_channel->zlib_glz_stat, zlib_stat_name);
stat_compress_init(&display_channel->jpeg_alpha_stat, jpeg_alpha_stat_name);
stat_compress_init(&display_channel->lz4_stat, lz4_stat_name);
}
static void guest_set_client_capabilities(RedWorker *worker)
@ -11591,6 +11742,11 @@ void handle_dev_set_compression(void *opaque, void *payload)
case SPICE_IMAGE_COMPRESS_QUIC:
spice_info("ic quic");
break;
#ifdef USE_LZ4
case SPICE_IMAGE_COMPRESS_LZ4:
spice_info("ic lz4");
break;
#endif
case SPICE_IMAGE_COMPRESS_LZ:
spice_info("ic lz");
break;
@ -11612,6 +11768,7 @@ void handle_dev_set_compression(void *opaque, void *payload)
stat_reset(&worker->display_channel->jpeg_stat);
stat_reset(&worker->display_channel->zlib_glz_stat);
stat_reset(&worker->display_channel->jpeg_alpha_stat);
stat_reset(&worker->display_channel->lz4_stat);
}
#endif
}
@ -12015,6 +12172,9 @@ SPICE_GNUC_NORETURN void *red_worker_main(void *arg)
red_init_quic(worker);
red_init_lz(worker);
red_init_jpeg(worker);
#ifdef USE_LZ4
red_init_lz4(worker);
#endif
red_init_zlib(worker);
worker->event_timeout = INF_EVENT_WAIT;
for (;;) {

View File

@ -74,6 +74,7 @@ typedef enum {
SPICE_IMAGE_COMPRESS_QUIC = 4,
SPICE_IMAGE_COMPRESS_GLZ = 5,
SPICE_IMAGE_COMPRESS_LZ = 6,
SPICE_IMAGE_COMPRESS_LZ4 = 7,
} spice_image_compression_t;
int spice_server_set_image_compression(SpiceServer *s,

@ -1 +1 @@
Subproject commit b3a00f4411962e0c06c7ad89a9936df388aa0023
Subproject commit 5b3cdad921d32c9294377efac61243a09f849d08