mirror of
https://github.com/kasmtech/KasmVNC.git
synced 2024-12-27 09:09:02 +01:00
335 lines
8.9 KiB
C++
335 lines
8.9 KiB
C++
|
/* Copyright (C) 2019 Kasm Web
|
||
|
*
|
||
|
* This is free software; you can redistribute it and/or modify
|
||
|
* it under the terms of the GNU General Public License as published by
|
||
|
* the Free Software Foundation; either version 2 of the License, or
|
||
|
* (at your option) any later version.
|
||
|
*
|
||
|
* This software 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 General Public License for more details.
|
||
|
*
|
||
|
* You should have received a copy of the GNU General Public License
|
||
|
* along with this software; if not, write to the Free Software
|
||
|
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
|
||
|
* USA.
|
||
|
*/
|
||
|
#include <rdr/OutStream.h>
|
||
|
#include <rfb/encodings.h>
|
||
|
#include <rfb/LogWriter.h>
|
||
|
#include <rfb/SConnection.h>
|
||
|
#include <rfb/ServerCore.h>
|
||
|
#include <rfb/PixelBuffer.h>
|
||
|
#include <rfb/TightWEBPEncoder.h>
|
||
|
#include <rfb/TightConstants.h>
|
||
|
#include <rfb/util.h>
|
||
|
#include <sys/time.h>
|
||
|
#include <stdlib.h>
|
||
|
|
||
|
#include <webp/encode.h>
|
||
|
|
||
|
using namespace rfb;
|
||
|
static LogWriter vlog("WEBP");
|
||
|
|
||
|
static const PixelFormat pfRGBX(32, 24, false, true, 255, 255, 255, 0, 8, 16);
|
||
|
static const PixelFormat pfBGRX(32, 24, false, true, 255, 255, 255, 16, 8, 0);
|
||
|
|
||
|
struct TightWEBPConfiguration {
|
||
|
uint8_t quality;
|
||
|
uint8_t method;
|
||
|
};
|
||
|
|
||
|
// Matt: I found that -q 40 -m 1 can compress an HD image in 78ms and the quality is
|
||
|
// acceptable and similar to cjpeg -q 70
|
||
|
|
||
|
// Using a test image from a browser, I matched DSSIM levels with the below JPEG
|
||
|
// targets. Qualities 6 and above couldn't match jpeg, presumably because
|
||
|
// webp always subsamples. Each level is 50-70% of the corresponding JPEG
|
||
|
// level's size, while approx matching its quality.
|
||
|
|
||
|
// NOTE: The JPEG quality and subsampling levels below were obtained
|
||
|
// experimentally by the VirtualGL Project. They represent the approximate
|
||
|
// average compression ratios listed below, as measured across the set of
|
||
|
// every 10th frame in the SPECviewperf 9 benchmark suite.
|
||
|
//
|
||
|
// 9 = JPEG quality 100, no subsampling (ratio ~= 10:1)
|
||
|
// [this should be lossless, except for round-off error]
|
||
|
// 8 = JPEG quality 92, no subsampling (ratio ~= 20:1)
|
||
|
// [this should be perceptually lossless, based on current research]
|
||
|
// 7 = JPEG quality 86, no subsampling (ratio ~= 25:1)
|
||
|
// 6 = JPEG quality 79, no subsampling (ratio ~= 30:1)
|
||
|
// 5 = JPEG quality 77, 4:2:2 subsampling (ratio ~= 40:1)
|
||
|
// 4 = JPEG quality 62, 4:2:2 subsampling (ratio ~= 50:1)
|
||
|
// 3 = JPEG quality 42, 4:2:2 subsampling (ratio ~= 60:1)
|
||
|
// 2 = JPEG quality 41, 4:2:0 subsampling (ratio ~= 70:1)
|
||
|
// 1 = JPEG quality 29, 4:2:0 subsampling (ratio ~= 80:1)
|
||
|
// 0 = JPEG quality 15, 4:2:0 subsampling (ratio ~= 100:1)
|
||
|
|
||
|
static const struct TightWEBPConfiguration conf[10] = {
|
||
|
{ 5, 0 }, // 0
|
||
|
{ 24, 0 }, // 1
|
||
|
{ 30, 0 }, // 2
|
||
|
{ 37, 0 }, // 3
|
||
|
{ 42, 1 }, // 4
|
||
|
{ 65, 1 }, // 5
|
||
|
{ 78, 1 }, // 6
|
||
|
{ 85, 2 }, // 7
|
||
|
{ 88, 3 }, // 8
|
||
|
{ 100, 4 } // 9
|
||
|
};
|
||
|
|
||
|
|
||
|
TightWEBPEncoder::TightWEBPEncoder(SConnection* conn) :
|
||
|
Encoder(conn, encodingTight, (EncoderFlags)(EncoderUseNativePF | EncoderLossy), -1),
|
||
|
qualityLevel(-1)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
TightWEBPEncoder::~TightWEBPEncoder()
|
||
|
{
|
||
|
}
|
||
|
|
||
|
bool TightWEBPEncoder::isSupported()
|
||
|
{
|
||
|
if (!conn->cp.supportsEncoding(encodingTight))
|
||
|
return false;
|
||
|
|
||
|
if (conn->cp.supportsWEBP)
|
||
|
return true;
|
||
|
|
||
|
// Tight support, but not WEBP
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
void TightWEBPEncoder::setQualityLevel(int level)
|
||
|
{
|
||
|
qualityLevel = level;
|
||
|
}
|
||
|
|
||
|
void TightWEBPEncoder::setFineQualityLevel(int quality, int subsampling)
|
||
|
{
|
||
|
// NOP
|
||
|
}
|
||
|
|
||
|
bool TightWEBPEncoder::treatLossless()
|
||
|
{
|
||
|
return qualityLevel >= rfb::Server::treatLossless;
|
||
|
}
|
||
|
|
||
|
void TightWEBPEncoder::compressOnly(const PixelBuffer* pb, const uint8_t qualityIn,
|
||
|
std::vector<uint8_t> &out, const bool lowVideoQuality) const
|
||
|
{
|
||
|
const rdr::U8* buffer;
|
||
|
int stride;
|
||
|
uint8_t quality, method;
|
||
|
WebPConfig cfg;
|
||
|
WebPPicture pic;
|
||
|
WebPMemoryWriter wrt;
|
||
|
|
||
|
buffer = pb->getBuffer(pb->getRect(), &stride);
|
||
|
|
||
|
if (lowVideoQuality) {
|
||
|
if (rfb::Server::webpVideoQuality == -1) {
|
||
|
quality = 3;
|
||
|
method = 0;
|
||
|
} else {
|
||
|
uint8_t num = rfb::Server::webpVideoQuality;
|
||
|
quality = conf[num].quality;
|
||
|
method = conf[num].method;
|
||
|
}
|
||
|
} else if (qualityIn <= 9) {
|
||
|
quality = conf[qualityIn].quality;
|
||
|
method = conf[qualityIn].method;
|
||
|
} else {
|
||
|
quality = 8;
|
||
|
method = 4;
|
||
|
}
|
||
|
|
||
|
WebPConfigInit(&cfg);
|
||
|
cfg.method = method;
|
||
|
cfg.quality = quality;
|
||
|
cfg.thread_level = 1; // Try to use multiple threads
|
||
|
|
||
|
WebPPictureInit(&pic);
|
||
|
pic.width = pb->getRect().width();
|
||
|
pic.height = pb->getRect().height();
|
||
|
|
||
|
if (pfRGBX.equal(pb->getPF())) {
|
||
|
WebPPictureImportRGBX(&pic, buffer, stride * 4);
|
||
|
} else if (pfBGRX.equal(pb->getPF())) {
|
||
|
WebPPictureImportBGRX(&pic, buffer, stride * 4);
|
||
|
} else {
|
||
|
rdr::U8* tmpbuf = new rdr::U8[pic.width * pic.height * 3];
|
||
|
pb->getPF().rgbFromBuffer(tmpbuf, (const rdr::U8 *) buffer, pic.width, stride, pic.height);
|
||
|
stride = pic.width * 3;
|
||
|
|
||
|
WebPPictureImportRGB(&pic, tmpbuf, stride);
|
||
|
delete [] tmpbuf;
|
||
|
}
|
||
|
|
||
|
WebPMemoryWriterInit(&wrt);
|
||
|
pic.writer = WebPMemoryWrite;
|
||
|
pic.custom_ptr = &wrt;
|
||
|
|
||
|
if (!WebPEncode(&cfg, &pic)) {
|
||
|
// Error
|
||
|
vlog.error("WEBP error %u", pic.error_code);
|
||
|
}
|
||
|
|
||
|
out.resize(wrt.size);
|
||
|
memcpy(&out[0], wrt.mem, wrt.size);
|
||
|
|
||
|
WebPPictureFree(&pic);
|
||
|
WebPMemoryWriterClear(&wrt);
|
||
|
}
|
||
|
|
||
|
void TightWEBPEncoder::writeOnly(const std::vector<uint8_t> &out) const
|
||
|
{
|
||
|
rdr::OutStream* os;
|
||
|
|
||
|
os = conn->getOutStream();
|
||
|
|
||
|
os->writeU8(tightWebp << 4);
|
||
|
|
||
|
writeCompact(out.size(), os);
|
||
|
os->writeBytes(&out[0], out.size());
|
||
|
}
|
||
|
|
||
|
void TightWEBPEncoder::writeRect(const PixelBuffer* pb, const Palette& palette)
|
||
|
{
|
||
|
const rdr::U8* buffer;
|
||
|
int stride;
|
||
|
uint8_t quality, method;
|
||
|
WebPConfig cfg;
|
||
|
WebPPicture pic;
|
||
|
WebPMemoryWriter wrt;
|
||
|
|
||
|
rdr::OutStream* os;
|
||
|
|
||
|
buffer = pb->getBuffer(pb->getRect(), &stride);
|
||
|
|
||
|
if (qualityLevel >= 0 && qualityLevel <= 9) {
|
||
|
quality = conf[qualityLevel].quality;
|
||
|
method = conf[qualityLevel].method;
|
||
|
} else {
|
||
|
quality = 8;
|
||
|
method = 4;
|
||
|
}
|
||
|
|
||
|
WebPConfigInit(&cfg);
|
||
|
cfg.method = method;
|
||
|
cfg.quality = quality;
|
||
|
cfg.thread_level = 1; // Try to use multiple threads
|
||
|
|
||
|
WebPPictureInit(&pic);
|
||
|
pic.width = pb->getRect().width();
|
||
|
pic.height = pb->getRect().height();
|
||
|
|
||
|
if (pfRGBX.equal(pb->getPF())) {
|
||
|
WebPPictureImportRGBX(&pic, buffer, stride * 4);
|
||
|
} else if (pfBGRX.equal(pb->getPF())) {
|
||
|
WebPPictureImportBGRX(&pic, buffer, stride * 4);
|
||
|
} else {
|
||
|
rdr::U8* tmpbuf = new rdr::U8[pic.width * pic.height * 3];
|
||
|
pb->getPF().rgbFromBuffer(tmpbuf, (const rdr::U8 *) buffer, pic.width, stride, pic.height);
|
||
|
stride = pic.width * 3;
|
||
|
|
||
|
WebPPictureImportRGB(&pic, tmpbuf, stride);
|
||
|
delete [] tmpbuf;
|
||
|
}
|
||
|
|
||
|
WebPMemoryWriterInit(&wrt);
|
||
|
pic.writer = WebPMemoryWrite;
|
||
|
pic.custom_ptr = &wrt;
|
||
|
|
||
|
if (!WebPEncode(&cfg, &pic)) {
|
||
|
// Error
|
||
|
vlog.error("WEBP error %u", pic.error_code);
|
||
|
}
|
||
|
|
||
|
os = conn->getOutStream();
|
||
|
|
||
|
os->writeU8(tightWebp << 4);
|
||
|
|
||
|
writeCompact(wrt.size, os);
|
||
|
os->writeBytes(wrt.mem, wrt.size);
|
||
|
|
||
|
WebPPictureFree(&pic);
|
||
|
WebPMemoryWriterClear(&wrt);
|
||
|
}
|
||
|
|
||
|
// How many milliseconds would it take to encode a 256x256 block at quality 8
|
||
|
rdr::U32 TightWEBPEncoder::benchmark() const
|
||
|
{
|
||
|
rdr::U8* buffer;
|
||
|
struct timeval start;
|
||
|
int stride, i;
|
||
|
const uint8_t quality = 8, method = 4;
|
||
|
WebPConfig cfg;
|
||
|
WebPPicture pic;
|
||
|
WebPMemoryWriter wrt;
|
||
|
ManagedPixelBuffer pb(pfRGBX, 256, 256);
|
||
|
|
||
|
buffer = pb.getBufferRW(pb.getRect(), &stride);
|
||
|
// Fill it with random data
|
||
|
for (i = 0; i < pb.getRect().width() * pb.getRect().height() * 4; i++)
|
||
|
buffer[i] = random();
|
||
|
|
||
|
gettimeofday(&start, NULL);
|
||
|
|
||
|
WebPConfigInit(&cfg);
|
||
|
cfg.method = method;
|
||
|
cfg.quality = quality;
|
||
|
cfg.thread_level = 1; // Try to use multiple threads
|
||
|
|
||
|
WebPPictureInit(&pic);
|
||
|
pic.width = pb.getRect().width();
|
||
|
pic.height = pb.getRect().height();
|
||
|
|
||
|
WebPPictureImportRGBX(&pic, buffer, stride * 4);
|
||
|
|
||
|
WebPMemoryWriterInit(&wrt);
|
||
|
pic.writer = WebPMemoryWrite;
|
||
|
pic.custom_ptr = &wrt;
|
||
|
|
||
|
if (!WebPEncode(&cfg, &pic)) {
|
||
|
// Error
|
||
|
vlog.error("WEBP error %u", pic.error_code);
|
||
|
}
|
||
|
|
||
|
WebPPictureFree(&pic);
|
||
|
WebPMemoryWriterClear(&wrt);
|
||
|
|
||
|
return msSince(&start);
|
||
|
}
|
||
|
|
||
|
void TightWEBPEncoder::writeSolidRect(int width, int height,
|
||
|
const PixelFormat& pf,
|
||
|
const rdr::U8* colour)
|
||
|
{
|
||
|
// FIXME: Add a shortcut in the JPEG compressor to handle this case
|
||
|
// without having to use the default fallback which is very slow.
|
||
|
Encoder::writeSolidRect(width, height, pf, colour);
|
||
|
}
|
||
|
|
||
|
void TightWEBPEncoder::writeCompact(rdr::U32 value, rdr::OutStream* os) const
|
||
|
{
|
||
|
// Copied from TightEncoder as it's overkill to inherit just for this
|
||
|
rdr::U8 b;
|
||
|
|
||
|
b = value & 0x7F;
|
||
|
if (value <= 0x7F) {
|
||
|
os->writeU8(b);
|
||
|
} else {
|
||
|
os->writeU8(b | 0x80);
|
||
|
b = value >> 7 & 0x7F;
|
||
|
if (value <= 0x3FFF) {
|
||
|
os->writeU8(b);
|
||
|
} else {
|
||
|
os->writeU8(b | 0x80);
|
||
|
os->writeU8(value >> 14 & 0xFF);
|
||
|
}
|
||
|
}
|
||
|
}
|