mirror of
https://github.com/kasmtech/KasmVNC.git
synced 2025-08-01 21:02:53 +02:00
356 lines
9.8 KiB
C++
356 lines
9.8 KiB
C++
/* Copyright (C) 2025 Kasm Technologies Corp
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include <memory>
|
|
#include <rdr/FileInStream.h>
|
|
#include <rfb/VNCServer.h>
|
|
|
|
#include "CConnection.h"
|
|
#include "CMsgReader.h"
|
|
#include "EncCache.h"
|
|
#include "EncodeManager.h"
|
|
#include "LogWriter.h"
|
|
#include "SMsgWriter.h"
|
|
|
|
namespace rdr {
|
|
class FileInStream;
|
|
}
|
|
|
|
extern "C" {
|
|
#include <libavcodec/avcodec.h>
|
|
#include <libavformat/avformat.h>
|
|
#include <libswscale/swscale.h>
|
|
}
|
|
|
|
static rfb::LogWriter vlog("Benchmarking");
|
|
|
|
struct AVFormatContextDeleter {
|
|
void operator()(AVFormatContext *ctx) const {
|
|
avformat_close_input(&ctx);
|
|
}
|
|
};
|
|
|
|
struct AVCodecContextDeleter {
|
|
void operator()(AVCodecContext *ctx) const {
|
|
avcodec_free_context(&ctx);
|
|
}
|
|
};
|
|
|
|
struct AVFrameDeleter {
|
|
void operator()(AVFrame *frame) const {
|
|
av_frame_free(&frame);
|
|
}
|
|
};
|
|
|
|
struct SwsContextDeleter {
|
|
void operator()(SwsContext *ctx) const {
|
|
sws_freeContext(ctx);
|
|
}
|
|
};
|
|
|
|
struct PacketDeleter {
|
|
void operator()(AVPacket *packet) const {
|
|
av_packet_free(&packet);
|
|
}
|
|
};
|
|
|
|
using FormatCtxGuard = std::unique_ptr<AVFormatContext, AVFormatContextDeleter>;
|
|
using CodecCtxGuard = std::unique_ptr<AVCodecContext, AVCodecContextDeleter>;
|
|
using FrameGuard = std::unique_ptr<AVFrame, AVFrameDeleter>;
|
|
using SwsContextGuard = std::unique_ptr<SwsContext, SwsContextDeleter>;
|
|
using PacketGuard = std::unique_ptr<AVPacket, PacketDeleter>;
|
|
|
|
|
|
namespace rfb {
|
|
class MockBufferStream : public rdr::BufferedInStream {
|
|
bool fillBuffer(size_t maxSize, bool wait) override {
|
|
return true;
|
|
}
|
|
};
|
|
|
|
class MockStream final : public rdr::OutStream {
|
|
public:
|
|
MockStream() {
|
|
offset = 0;
|
|
ptr = buf;
|
|
end = buf + sizeof(buf);
|
|
}
|
|
|
|
private:
|
|
void overrun(size_t needed) override {
|
|
flush();
|
|
|
|
/*if (itemSize * nItems > end - ptr)
|
|
nItems = (end - ptr) / itemSize;
|
|
return nItems;*/
|
|
}
|
|
|
|
public:
|
|
size_t length() override {
|
|
flush();
|
|
return offset;
|
|
}
|
|
|
|
void flush() override {
|
|
offset += ptr - buf;
|
|
ptr = buf;
|
|
}
|
|
|
|
private:
|
|
ptrdiff_t offset;
|
|
rdr::U8 buf[131072]{};
|
|
};
|
|
|
|
class MockSConnection : public SConnection {
|
|
public:
|
|
MockSConnection() {
|
|
setStreams(nullptr, &out);
|
|
|
|
setWriter(new SMsgWriter(&cp, &out, &udps));
|
|
}
|
|
|
|
~MockSConnection() override = default;
|
|
|
|
void writeUpdate(const UpdateInfo &ui, const rfb::PixelBuffer *pb) {
|
|
vlog.info("Writing update");
|
|
// Drop any lossy tracking that is now outside the framebuffer
|
|
//manager.pruneLosslessRefresh(Region(pb->getRect()));
|
|
|
|
bytes += out.length();
|
|
udp_bytes += udps.length();
|
|
|
|
vlog.info("bytes written: %lu", bytes);
|
|
vlog.info("udp bytes written: %lu", udp_bytes);
|
|
|
|
cache.clear();
|
|
manager.writeUpdate(ui, pb, nullptr);
|
|
}
|
|
|
|
virtual void setAccessRights(AccessRights ar) {
|
|
}
|
|
|
|
void setDesktopSize(int fb_width, int fb_height,
|
|
const rfb::ScreenSet &layout) override {
|
|
cp.width = fb_width;
|
|
cp.height = fb_height;
|
|
cp.screenLayout = layout;
|
|
|
|
writer()->writeExtendedDesktopSize();
|
|
writer()->writeSetDesktopSize();
|
|
}
|
|
|
|
void sendStats(const bool toClient) override {
|
|
}
|
|
|
|
[[nodiscard]] bool canChangeKasmSettings() const override {
|
|
return false;
|
|
}
|
|
|
|
void udpUpgrade(const char *resp) override {
|
|
}
|
|
|
|
void udpDowngrade(const bool) override {
|
|
}
|
|
|
|
void subscribeUnixRelay(const char *name) override {
|
|
}
|
|
|
|
void unixRelay(const char *name, const rdr::U8 *buf, const unsigned len) override {
|
|
}
|
|
|
|
void handleFrameStats(rdr::U32 all, rdr::U32 render) override {
|
|
}
|
|
|
|
[[nodiscard]] auto getJpegStats() const {
|
|
return manager.jpegstats;
|
|
}
|
|
|
|
[[nodiscard]] auto getWebPStats() const {
|
|
return manager.webpstats;
|
|
}
|
|
|
|
uint64_t bytes;
|
|
uint64_t udp_bytes;
|
|
|
|
protected:
|
|
MockStream out{};
|
|
MockStream udps{};
|
|
|
|
EncCache cache{};
|
|
EncodeManager manager{this, &cache};
|
|
};
|
|
|
|
static constexpr rdr::S32 encodings[] = {
|
|
pseudoEncodingWEBP,
|
|
pseudoEncodingJpegVideoQualityLevel0,
|
|
pseudoEncodingJpegVideoQualityLevel9,
|
|
pseudoEncodingWebpVideoQualityLevel0,
|
|
pseudoEncodingWebpVideoQualityLevel9,
|
|
pseudoEncodingTreatLosslessLevel0,
|
|
pseudoEncodingTreatLosslessLevel10,
|
|
pseudoEncodingPreferBandwidth,
|
|
pseudoEncodingDynamicQualityMinLevel0,
|
|
pseudoEncodingDynamicQualityMinLevel9,
|
|
pseudoEncodingDynamicQualityMaxLevel0,
|
|
pseudoEncodingDynamicQualityMaxLevel9,
|
|
pseudoEncodingVideoAreaLevel1,
|
|
pseudoEncodingVideoAreaLevel100,
|
|
pseudoEncodingVideoTimeLevel0,
|
|
pseudoEncodingVideoTimeLevel100,
|
|
|
|
pseudoEncodingFrameRateLevel10,
|
|
pseudoEncodingFrameRateLevel60,
|
|
pseudoEncodingMaxVideoResolution,
|
|
pseudoEncodingVideoScalingLevel0,
|
|
pseudoEncodingVideoScalingLevel9,
|
|
pseudoEncodingVideoOutTimeLevel1,
|
|
pseudoEncodingVideoOutTimeLevel100,
|
|
pseudoEncodingQOI
|
|
};
|
|
|
|
class MockCConnection final : public CConnection {
|
|
public:
|
|
explicit MockCConnection(ManagedPixelBuffer *pb) {
|
|
setStreams(&in, nullptr);
|
|
|
|
// Need to skip the initial handshake and ServerInit
|
|
setState(RFBSTATE_NORMAL);
|
|
// That also means that the reader and writer weren't setup
|
|
setReader(new rfb::CMsgReader(this, &in));
|
|
auto &pf = pb->getPF();
|
|
CMsgHandler::setPixelFormat(pf);
|
|
|
|
MockCConnection::setDesktopSize(pb->width(), pb->height());
|
|
setFramebuffer(pb);
|
|
|
|
cp.setPF(pf);
|
|
|
|
sc.cp.setPF(pf);
|
|
sc.setEncodings(std::size(encodings), encodings);
|
|
}
|
|
|
|
void setCursor(int width, int height, const Point &hotspot, const rdr::U8 *data, const bool resizing) override {
|
|
}
|
|
|
|
~MockCConnection() override = default;
|
|
|
|
|
|
struct stats_t {
|
|
EncodeManager::codecstats_t jpeg_stats;
|
|
EncodeManager::codecstats_t webp_stats;
|
|
uint64_t bytes;
|
|
uint64_t udp_bytes;
|
|
};
|
|
|
|
[[nodiscard]] stats_t getStats() const {
|
|
return {
|
|
sc.getJpegStats(),
|
|
sc.getWebPStats(),
|
|
sc.bytes,
|
|
sc.udp_bytes
|
|
};
|
|
}
|
|
|
|
void setDesktopSize(int w, int h) override {
|
|
CConnection::setDesktopSize(w, h);
|
|
|
|
if (screen_layout.num_screens())
|
|
screen_layout.remove_screen(0);
|
|
|
|
screen_layout.add_screen(Screen(0, 0, 0, w, h, 0));
|
|
//cp.screenLayout = screen_layout;
|
|
//sc.setDesktopSize(w, h, screen_layout);
|
|
vlog.info("setDesktopSize");
|
|
}
|
|
|
|
void setNewFrame(const AVFrame *frame) {
|
|
auto *pb = getFramebuffer();
|
|
const int width = pb->width();
|
|
const int height = pb->height();
|
|
const rfb::Rect rect(0, 0, width, height);
|
|
|
|
int dstStride;
|
|
auto *buffer = pb->getBufferRW(rect, &dstStride);
|
|
|
|
const PixelFormat &pf = pb->getPF();
|
|
|
|
// Source data and stride from FFmpeg
|
|
const auto *srcData = frame->data[0];
|
|
const int srcStride = frame->linesize[0] / 3; // Convert bytes to pixels
|
|
|
|
vlog.info("Frame stride %d", srcStride);
|
|
|
|
// Convert from RGB format to the PixelBuffer's format
|
|
pf.bufferFromRGB(buffer, srcData, width, srcStride, height);
|
|
|
|
// Commit changes
|
|
pb->commitBufferRW(rect);
|
|
}
|
|
|
|
void framebufferUpdateStart() override {
|
|
CConnection::framebufferUpdateStart();
|
|
|
|
updates.clear();
|
|
}
|
|
|
|
void framebufferUpdateEnd() override {
|
|
const PixelBuffer *pb = getFramebuffer();
|
|
|
|
UpdateInfo ui;
|
|
|
|
const Region clip(pb->getRect());
|
|
|
|
CConnection::framebufferUpdateEnd();
|
|
|
|
//updates.add_changed(pb->getRect());
|
|
updates.getUpdateInfo(&ui, clip);
|
|
vlog.info("%d", ui.changed.numRects());
|
|
vlog.info("%d", ui.copied.numRects());
|
|
vlog.info("%lu", ui.copypassed.size());
|
|
sc.writeUpdate(ui, pb);
|
|
}
|
|
|
|
void dataRect(const Rect &r, int encoding) override {
|
|
CConnection::dataRect(r, encoding);
|
|
|
|
if (encoding != encodingCopyRect) // FIXME
|
|
updates.add_changed(Region(r));
|
|
}
|
|
|
|
void setColourMapEntries(int, int, rdr::U16 *) override {
|
|
}
|
|
|
|
void bell() override {
|
|
}
|
|
|
|
void serverCutText(const char *, rdr::U32) override {
|
|
}
|
|
|
|
void serverCutText(const char *str) override {
|
|
}
|
|
|
|
protected:
|
|
MockBufferStream in;
|
|
ScreenSet screen_layout;
|
|
SimpleUpdateTracker updates;
|
|
MockSConnection sc;
|
|
};
|
|
}
|