[feat] write and load thumbnails supported

This commit is contained in:
dijunkun
2024-11-07 16:29:02 +08:00
parent e3c2e9ec6d
commit 4c6159e4d4
8 changed files with 300 additions and 123 deletions

View File

@@ -100,3 +100,26 @@ std::string GetMac() {
#endif #endif
return mac_addr; return mac_addr;
} }
std::string GetHostName() {
char hostname[256];
#ifdef _WIN32
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
std::cerr << "WSAStartup failed." << std::endl;
return "";
}
if (gethostname(hostname, sizeof(hostname)) == SOCKET_ERROR) {
LOG_ERROR("gethostname failed: {}", WSAGetLastError());
WSACleanup();
return "";
}
WSACleanup();
#else
if (gethostname(hostname, sizeof(hostname)) == -1) {
LOG_ERROR("gethostname failed");
return "";
}
#endif
return hostname;
}

View File

@@ -10,5 +10,6 @@
#include <iostream> #include <iostream>
std::string GetMac(); std::string GetMac();
std::string GetHostName();
#endif #endif

View File

@@ -9,7 +9,7 @@
#include <stdio.h> #include <stdio.h>
typedef enum { mouse = 0, keyboard, audio_capture } ControlType; typedef enum { mouse = 0, keyboard, audio_capture, host_info } ControlType;
typedef enum { move = 0, left_down, left_up, right_down, right_up } MouseFlag; typedef enum { move = 0, left_down, left_up, right_down, right_up } MouseFlag;
typedef enum { key_down = 0, key_up } KeyFlag; typedef enum { key_down = 0, key_up } KeyFlag;
typedef struct { typedef struct {
@@ -23,11 +23,17 @@ typedef struct {
KeyFlag flag; KeyFlag flag;
} Key; } Key;
typedef struct {
char host_name[64];
size_t host_name_size;
} HostInfo;
typedef struct { typedef struct {
ControlType type; ControlType type;
union { union {
Mouse m; Mouse m;
Key k; Key k;
HostInfo i;
bool a; bool a;
}; };
} RemoteAction; } RemoteAction;

View File

@@ -1,5 +1,6 @@
#include "render.h" #include "render.h"
#include <filesystem>
#include <fstream> #include <fstream>
#include <iostream> #include <iostream>
#include <string> #include <string>
@@ -15,12 +16,6 @@
#include "rd_log.h" #include "rd_log.h"
#include "screen_capturer_factory.h" #include "screen_capturer_factory.h"
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "libyuv.h"
#include "stb_image_write.h"
// Refresh Event // Refresh Event
#define REFRESH_EVENT (SDL_USEREVENT + 1) #define REFRESH_EVENT (SDL_USEREVENT + 1)
#define NV12_BUFFER_SIZE 1280 * 720 * 3 / 2 #define NV12_BUFFER_SIZE 1280 * 720 * 3 / 2
@@ -75,61 +70,6 @@ SDL_HitTestResult Render::HitTestCallback(SDL_Window* window,
return SDL_HITTEST_NORMAL; return SDL_HITTEST_NORMAL;
} }
bool LoadTextureFromMemory(const void* data, size_t data_size,
SDL_Renderer* renderer, SDL_Texture** out_texture,
int* out_width, int* out_height) {
int image_width = 0;
int image_height = 0;
int channels = 4;
unsigned char* image_data =
stbi_load_from_memory((const unsigned char*)data, (int)data_size,
&image_width, &image_height, NULL, 4);
if (image_data == nullptr) {
fprintf(stderr, "Failed to load image: %s\n", stbi_failure_reason());
return false;
}
SDL_Surface* surface = SDL_CreateRGBSurfaceFrom(
(void*)image_data, image_width, image_height, channels * 8,
channels * image_width, 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000);
if (surface == nullptr) {
fprintf(stderr, "Failed to create SDL surface: %s\n", SDL_GetError());
return false;
}
SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface);
if (texture == nullptr)
fprintf(stderr, "Failed to create SDL texture: %s\n", SDL_GetError());
*out_texture = texture;
*out_width = image_width;
*out_height = image_height;
SDL_FreeSurface(surface);
stbi_image_free(image_data);
return true;
}
// Open and read a file, then forward to LoadTextureFromMemory()
bool LoadTextureFromFile(const char* file_name, SDL_Renderer* renderer,
SDL_Texture** out_texture, int* out_width,
int* out_height) {
FILE* f = fopen(file_name, "rb");
if (f == NULL) return false;
fseek(f, 0, SEEK_END);
size_t file_size = (size_t)ftell(f);
if (file_size == -1) return false;
fseek(f, 0, SEEK_SET);
void* file_data = IM_ALLOC(file_size);
fread(file_data, 1, file_size, f);
bool ret = LoadTextureFromMemory(file_data, file_size, renderer, out_texture,
out_width, out_height);
IM_FREE(file_data);
fclose(f);
return ret;
}
Render::Render() {} Render::Render() {}
Render::~Render() {} Render::~Render() {}
@@ -701,47 +641,6 @@ int Render::DrawMainWindow() {
return 0; return 0;
} }
void ScaleYUV420pToABGR(char* dst_buffer_, int video_width_, int video_height_,
int scaled_video_width_, int scaled_video_height_,
char* argb_buffer_) {
int src_y_size = video_width_ * video_height_;
int src_uv_size = (video_width_ + 1) / 2 * (video_height_ + 1) / 2;
int dst_y_size = scaled_video_width_ * scaled_video_height_;
int dst_uv_size =
(scaled_video_width_ + 1) / 2 * (scaled_video_height_ + 1) / 2;
uint8_t* src_y = reinterpret_cast<uint8_t*>(dst_buffer_);
uint8_t* src_u = src_y + src_y_size;
uint8_t* src_v = src_u + src_uv_size;
std::unique_ptr<uint8_t[]> dst_y(new uint8_t[dst_y_size]);
std::unique_ptr<uint8_t[]> dst_u(new uint8_t[dst_uv_size]);
std::unique_ptr<uint8_t[]> dst_v(new uint8_t[dst_uv_size]);
try {
libyuv::I420Scale(src_y, video_width_, src_u, (video_width_ + 1) / 2, src_v,
(video_width_ + 1) / 2, video_width_, video_height_,
dst_y.get(), scaled_video_width_, dst_u.get(),
(scaled_video_width_ + 1) / 2, dst_v.get(),
(scaled_video_width_ + 1) / 2, scaled_video_width_,
scaled_video_height_, libyuv::kFilterBilinear);
} catch (const std::exception& e) {
LOG_ERROR("I420Scale failed: %s", e.what());
return;
}
try {
libyuv::I420ToABGR(
dst_y.get(), scaled_video_width_, dst_u.get(),
(scaled_video_width_ + 1) / 2, dst_v.get(),
(scaled_video_width_ + 1) / 2, reinterpret_cast<uint8_t*>(argb_buffer_),
scaled_video_width_ * 4, scaled_video_width_, scaled_video_height_);
} catch (const std::exception& e) {
LOG_ERROR("I420ToBGRA failed: %s", e.what());
return;
}
}
int Render::DrawStreamWindow() { int Render::DrawStreamWindow() {
if (!stream_ctx_) { if (!stream_ctx_) {
LOG_ERROR("Stream context is null"); LOG_ERROR("Stream context is null");
@@ -842,8 +741,8 @@ int Render::Run() {
CreateMainWindow(); CreateMainWindow();
SetupMainWindow(); SetupMainWindow();
const int scaled_video_width_ = 128; const int scaled_video_width_ = 160;
const int scaled_video_height_ = 72; const int scaled_video_height_ = 90;
char* argb_buffer_ = char* argb_buffer_ =
new char[scaled_video_width_ * scaled_video_height_ * 40]; new char[scaled_video_width_ * scaled_video_height_ * 40];
@@ -904,13 +803,9 @@ int Render::Run() {
DestroyStreamWindowContext(); DestroyStreamWindowContext();
if (dst_buffer_) { if (dst_buffer_) {
ScaleYUV420pToABGR((char*)dst_buffer_, video_width_, video_height_, thumbnail_.SaveToThumbnail((char*)dst_buffer_, video_width_,
scaled_video_width_, scaled_video_height_, video_height_, host_name_, remote_id_);
argb_buffer_); recent_connection_image_save_time_ = SDL_GetTicks();
stbi_write_png("RecentConnectionImage01.png", scaled_video_width_,
scaled_video_height_, 4, argb_buffer_,
scaled_video_width_ * 4);
LOG_ERROR("RecentConnectionImage01.png saved");
} }
LOG_INFO("[{}] Leave connection [{}]", client_id_, remote_id_); LOG_INFO("[{}] Leave connection [{}]", client_id_, remote_id_);
@@ -1013,19 +908,18 @@ int Render::Run() {
} }
if (reload_recent_connections_ && main_renderer_) { if (reload_recent_connections_ && main_renderer_) {
bool ret = LoadTextureFromFile( // loal recent connection thumbnails after saving for 1 second
"RecentConnectionImage01.png", main_renderer_, uint32_t now_time = SDL_GetTicks();
&recent_connection_texture_, &recent_connection_image_width_, if (now_time - recent_connection_image_save_time_ >= 1000) {
&recent_connection_image_height_); int ret = thumbnail_.LoadThumbnail(
main_renderer_, &recent_connection_texture_,
&recent_connection_image_width_, &recent_connection_image_height_);
if (!ret) { if (!ret) {
LOG_ERROR("Load recent connections image failed"); LOG_INFO("Load recent connection thumbnails");
} else {
LOG_ERROR("Load recent connections image width: {}, height: {}",
recent_connection_image_width_,
recent_connection_image_height_);
} }
reload_recent_connections_ = false; reload_recent_connections_ = false;
} }
}
if (connection_established_ && streaming_) { if (connection_established_ && streaming_) {
CreateStreamWindow(); CreateStreamWindow();

View File

@@ -21,6 +21,7 @@
#include "imgui_impl_sdlrenderer2.h" #include "imgui_impl_sdlrenderer2.h"
#include "screen_capturer_factory.h" #include "screen_capturer_factory.h"
#include "speaker_capturer_factory.h" #include "speaker_capturer_factory.h"
#include "thumbnail.h"
class Render { class Render {
public: public:
@@ -227,11 +228,16 @@ class Render {
SDL_Texture *recent_connection_texture_ = nullptr; SDL_Texture *recent_connection_texture_ = nullptr;
int recent_connection_image_width_ = 128; int recent_connection_image_width_ = 128;
int recent_connection_image_height_ = 72; int recent_connection_image_height_ = 72;
uint32_t recent_connection_image_save_time_ = 0;
// video window // video window
SDL_Texture *stream_texture_ = nullptr; SDL_Texture *stream_texture_ = nullptr;
SDL_Rect stream_render_rect_; SDL_Rect stream_render_rect_;
uint32_t stream_pixformat_ = 0; uint32_t stream_pixformat_ = 0;
std::string host_name_ = "";
std::string image_path_ = "thumbnails";
Thumbnail thumbnail_;
bool resizable_ = false; bool resizable_ = false;
bool label_inited_ = false; bool label_inited_ = false;
@@ -268,6 +274,7 @@ class Render {
bool is_control_bar_in_left_ = true; bool is_control_bar_in_left_ = true;
bool control_window_width_is_changing_ = false; bool control_window_width_is_changing_ = false;
bool reload_recent_connections_ = true; bool reload_recent_connections_ = true;
bool hostname_sent_ = false;
double copy_start_time_ = 0; double copy_start_time_ = 0;
double regenerate_password_start_time_ = 0; double regenerate_password_start_time_ = 0;

View File

@@ -1,5 +1,6 @@
#include "device_controller.h" #include "device_controller.h"
#include "localization.h" #include "localization.h"
#include "platform.h"
#include "rd_log.h" #include "rd_log.h"
#include "render.h" #include "render.h"
@@ -209,6 +210,11 @@ void Render::OnReceiveDataBufferCb(const char *data, size_t size,
} else { } else {
render->StopSpeakerCapture(); render->StopSpeakerCapture();
} }
} else if (ControlType::keyboard == remote_action.type) {
} else if (ControlType::host_info == remote_action.type) {
render->host_name_ =
std::string(remote_action.i.host_name, remote_action.i.host_name_size);
LOG_INFO("Remote hostname: [{}]", render->host_name_);
} }
} }
@@ -261,6 +267,18 @@ void Render::OnConnectionStatusCb(ConnectionStatus status, const char *user_id,
render->start_screen_capture_ = true; render->start_screen_capture_ = true;
render->start_mouse_control_ = true; render->start_mouse_control_ = true;
} }
if (!render->hostname_sent_) {
std::string host_name = GetHostName();
RemoteAction remote_action;
remote_action.type = ControlType::host_info;
memcpy(&remote_action.i.host_name, host_name.data(), host_name.size());
remote_action.i.host_name_size = host_name.size();
int ret = SendData(render->peer_, DATA_TYPE::DATA,
(const char *)&remote_action, sizeof(remote_action));
if (0 == ret) {
render->hostname_sent_ = true;
}
}
} else if (ConnectionStatus::Disconnected == status) { } else if (ConnectionStatus::Disconnected == status) {
render->connection_status_str_ = "Disconnected"; render->connection_status_str_ = "Disconnected";
render->password_validating_time_ = 0; render->password_validating_time_ = 0;
@@ -274,6 +292,7 @@ void Render::OnConnectionStatusCb(ConnectionStatus status, const char *user_id,
render->start_mouse_control_ = false; render->start_mouse_control_ = false;
render->connection_established_ = false; render->connection_established_ = false;
render->control_mouse_ = false; render->control_mouse_ = false;
render->hostname_sent_ = false;
if (render->audio_capture_) { if (render->audio_capture_) {
render->StopSpeakerCapture(); render->StopSpeakerCapture();
render->audio_capture_ = false; render->audio_capture_ = false;

View File

@@ -0,0 +1,187 @@
#include "thumbnail.h"
#include <chrono>
#include <fstream>
#include <map>
#include "libyuv.h"
#include "rd_log.h"
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image_write.h"
void ScaleYUV420pToABGR(char* dst_buffer_, int video_width_, int video_height_,
int scaled_video_width_, int scaled_video_height_,
char* rgba_buffer_) {
int src_y_size = video_width_ * video_height_;
int src_uv_size = (video_width_ + 1) / 2 * (video_height_ + 1) / 2;
int dst_y_size = scaled_video_width_ * scaled_video_height_;
int dst_uv_size =
(scaled_video_width_ + 1) / 2 * (scaled_video_height_ + 1) / 2;
uint8_t* src_y = reinterpret_cast<uint8_t*>(dst_buffer_);
uint8_t* src_u = src_y + src_y_size;
uint8_t* src_v = src_u + src_uv_size;
std::unique_ptr<uint8_t[]> dst_y(new uint8_t[dst_y_size]);
std::unique_ptr<uint8_t[]> dst_u(new uint8_t[dst_uv_size]);
std::unique_ptr<uint8_t[]> dst_v(new uint8_t[dst_uv_size]);
try {
libyuv::I420Scale(src_y, video_width_, src_u, (video_width_ + 1) / 2, src_v,
(video_width_ + 1) / 2, video_width_, video_height_,
dst_y.get(), scaled_video_width_, dst_u.get(),
(scaled_video_width_ + 1) / 2, dst_v.get(),
(scaled_video_width_ + 1) / 2, scaled_video_width_,
scaled_video_height_, libyuv::kFilterBilinear);
} catch (const std::exception& e) {
LOG_ERROR("I420Scale failed: %s", e.what());
return;
}
try {
libyuv::I420ToABGR(
dst_y.get(), scaled_video_width_, dst_u.get(),
(scaled_video_width_ + 1) / 2, dst_v.get(),
(scaled_video_width_ + 1) / 2, reinterpret_cast<uint8_t*>(rgba_buffer_),
scaled_video_width_ * 4, scaled_video_width_, scaled_video_height_);
} catch (const std::exception& e) {
LOG_ERROR("I420ToRGBA failed: %s", e.what());
return;
}
}
Thumbnail::Thumbnail() { std::filesystem::create_directory(image_path_); }
Thumbnail::~Thumbnail() {
if (rgba_buffer_) {
delete[] rgba_buffer_;
rgba_buffer_ = nullptr;
}
}
int Thumbnail::SaveToThumbnail(const char* yuv420p, int width, int height,
const std::string& host_name,
const std::string& remote_id) {
if (!rgba_buffer_) {
rgba_buffer_ = new char[thumbnail_width_ * thumbnail_height_ * 4];
}
if (yuv420p) {
ScaleYUV420pToABGR((char*)yuv420p, width, height, thumbnail_width_,
thumbnail_height_, rgba_buffer_);
std::string image_name =
image_path_ + "/" + host_name + "@" + remote_id + ".png";
stbi_write_png(image_name.data(), thumbnail_width_, thumbnail_height_, 4,
rgba_buffer_, thumbnail_width_ * 4);
}
return 0;
}
bool LoadTextureFromMemory(const void* data, size_t data_size,
SDL_Renderer* renderer, SDL_Texture** out_texture,
int* out_width, int* out_height) {
int image_width = 0;
int image_height = 0;
int channels = 4;
unsigned char* image_data =
stbi_load_from_memory((const unsigned char*)data, (int)data_size,
&image_width, &image_height, NULL, 4);
if (image_data == nullptr) {
fprintf(stderr, "Failed to load image: %s\n", stbi_failure_reason());
return false;
}
// ABGR
SDL_Surface* surface = SDL_CreateRGBSurfaceFrom(
(void*)image_data, image_width, image_height, channels * 8,
channels * image_width, 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000);
if (surface == nullptr) {
fprintf(stderr, "Failed to create SDL surface: %s\n", SDL_GetError());
return false;
}
SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface);
if (texture == nullptr)
fprintf(stderr, "Failed to create SDL texture: %s\n", SDL_GetError());
*out_texture = texture;
*out_width = image_width;
*out_height = image_height;
SDL_FreeSurface(surface);
stbi_image_free(image_data);
return true;
}
bool LoadTextureFromFile(const char* file_name, SDL_Renderer* renderer,
SDL_Texture** out_texture, int* out_width,
int* out_height) {
std::filesystem::path file_path(file_name);
if (!std::filesystem::exists(file_path)) return false;
std::ifstream file(file_path, std::ios::binary);
if (!file) return false;
file.seekg(0, std::ios::end);
size_t file_size = file.tellg();
file.seekg(0, std::ios::beg);
if (file_size == -1) return false;
char* file_data = new char[file_size];
if (!file_data) return false;
file.read(file_data, file_size);
bool ret = LoadTextureFromMemory(file_data, file_size, renderer, out_texture,
out_width, out_height);
delete[] file_data;
return ret;
}
std::vector<std::filesystem::path> Thumbnail::FindThumbnailPath(
const std::filesystem::path& directory) {
std::vector<std::filesystem::path> thumbnails_path;
std::string image_extensions = ".png";
if (!std::filesystem::is_directory(directory)) {
LOG_ERROR("No such directory [{}]", directory.string());
return thumbnails_path;
}
thumbnails_sorted_by_write_time_.clear();
for (const auto& entry : std::filesystem::directory_iterator(directory)) {
if (entry.is_regular_file()) {
std::time_t last_write_time = std::chrono::system_clock::to_time_t(
time_point_cast<std::chrono::system_clock::duration>(
entry.last_write_time() -
std::filesystem::file_time_type::clock::now() +
std::chrono::system_clock::now()));
if (entry.path().extension() == image_extensions) {
thumbnails_sorted_by_write_time_[last_write_time] = entry.path();
}
for (const auto& pair : thumbnails_sorted_by_write_time_) {
thumbnails_path.push_back(pair.second);
}
}
}
return thumbnails_path;
}
int Thumbnail::LoadThumbnail(SDL_Renderer* renderer, SDL_Texture** texture,
int* width, int* height) {
std::vector<std::filesystem::path> image_path =
FindThumbnailPath(image_path_);
if (image_path.size() == 0) {
LOG_INFO("No thumbnail saved", image_path_);
return -1;
} else {
LoadTextureFromFile(image_path[0].string().c_str(), renderer, texture,
width, height);
return 0;
}
}

View File

@@ -0,0 +1,40 @@
/*
* @Author: DI JUNKUN
* @Date: 2024-11-07
* Copyright (c) 2024 by DI JUNKUN, All Rights Reserved.
*/
#ifndef _THUMBNAIL_H_
#define _THUMBNAIL_H_
#include <SDL.h>
#include <filesystem>
#include <map>
class Thumbnail {
public:
Thumbnail();
~Thumbnail();
public:
int SaveToThumbnail(const char* yuv420p, int width, int height,
const std::string& host_name,
const std::string& remote_id);
int LoadThumbnail(SDL_Renderer* renderer, SDL_Texture** texture, int* width,
int* height);
private:
std::vector<std::filesystem::path> FindThumbnailPath(
const std::filesystem::path& directory);
private:
int thumbnail_width_ = 160;
int thumbnail_height_ = 90;
char* rgba_buffer_ = nullptr;
std::string image_path_ = "thumbnails";
std::map<std::time_t, std::filesystem::path> thumbnails_sorted_by_write_time_;
};
#endif