f8ead215d8
build-windows / build-hello-agent-x64 (push) Successful in 5m41s
A single-binary, Flutter-free remote-support agent that speaks the stock
RustDesk wire protocol. Designed for one-line MDM deployment against a
self-hosted rustdesk-server: a supporter using the unmodified rustdesk.exe
client connects, the controlled-side user gets a native Win32 approval
prompt, click Yes / No.
CLI surface
hello-agent.exe --install # register + start service
hello-agent.exe --uninstall # stop, delete, clean up
hello-agent.exe --config <BLOB> # admin-UI deploy string
hello-agent.exe --install --config <BLOB> # MDM one-liner
--config accepts both forms emitted by the rustdesk-server admin UI: the
reversed-base64 deploy string and the host=,key=,api=,relay= filename
form. Decoded via the upstream custom_server module, persisted via
hbb_common::config::Config::set_option.
Architecture
--service runs as a Session 0 LocalSystem service. It polls
WTSGetActiveConsoleSessionId and (re)spawns hello-agent.exe --server
into the active console session via librustdesk::platform::run_as_user,
handling the Session 0 → user-session token impersonation.
--server is the worker. It boots three concurrent components:
1. cm_popup: an IPC listener on the rustdesk `_cm` named pipe
2. librustdesk::start_server(true, false): the upstream protocol
stack — rendezvous mediator, NAT punch, IPC server, screen
capture, login validation, hbbs_http heartbeat / sysinfo sync
3. (implicit) ApproveMode::Click is pinned in config, so every
incoming connection routes through cm_popup
The popup mechanism reuses an existing upstream contract without any
patches to the protocol code: when a peer connects with no password,
Connection::start in the upstream code calls try_start_cm_ipc, which
ipc::connect-s the `_cm` pipe before falling back to spawning a Flutter
CM child. Since cm_popup is up first, step 1 succeeds; we read the
Data::Login{authorized:false} frame, show MessageBoxTimeoutW (Yes/No,
60s, top-most, system-modal), and reply Data::Authorize or Data::Close.
Source tree
src/main.rs CLI dispatcher + run_server() composition
src/cli.rs hand-rolled argv parser + unit tests
src/service.rs windows-service install/uninstall/dispatcher
src/config_import.rs --config blob decoding + persistence
src/cm_popup.rs _cm IPC listener + Win32 approval dialog
Vendoring
The upstream RustDesk crate is vendored under vendor/rustdesk/ — full
workspace including libs/{hbb_common, scrap, enigo, clipboard,
virtual_display, remote_printer}. This makes the build self-contained
(no submodules, no sibling-repo checkout in CI) and gives us freedom to
fork in a different direction later. Excluded from the vendor: .git,
target/, flutter/, appimage/, flatpak/, fastlane/, docs/, examples/,
ci/, build.py, Dockerfile, upstream README/CLAUDE/AGENTS/GEMINI.
One local divergence vs. upstream: vendor/rustdesk/src/lib.rs flips
`mod custom_server` → `pub mod custom_server` so config_import.rs can
call get_custom_server_from_string without going through the
ui_interface shim. Documented in README.md → "Re-syncing the vendored
copy".
CI
.gitea/workflows/build-windows.yml builds on a self-hosted Windows
runner with Rust 1.75, LLVM 15.0.6 (libclang for bindgen via libvpx-sys),
and a vcpkg cache. The vendored vcpkg.json drives x64-windows-static
deps. The workflow stages the resulting hello-agent.exe into
SignOutput\, reports authenticode signing status (warns on unsigned),
and uploads as artifact. ~15 min full build, faster on incremental.
Out of scope for this commit: Linux/macOS builds, code signing, MSI
packaging, coexistence with stock rustdesk on the same box (currently
shares the RustDesk APP_NAME and config dir).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
247 lines
7.9 KiB
Diff
247 lines
7.9 KiB
Diff
From 7323bd68c1b34e9298ea557ff7a3e1883b653957 Mon Sep 17 00:00:00 2001
|
|
From: 21pages <sunboeasy@gmail.com>
|
|
Date: Tue, 10 Dec 2024 14:28:16 +0800
|
|
Subject: [PATCH 4/5] mediacodec changing bitrate
|
|
|
|
Signed-off-by: 21pages <sunboeasy@gmail.com>
|
|
---
|
|
libavcodec/mediacodec_wrapper.c | 98 +++++++++++++++++++++++++++++++++
|
|
libavcodec/mediacodec_wrapper.h | 7 +++
|
|
libavcodec/mediacodecenc.c | 18 ++++++
|
|
3 files changed, 123 insertions(+)
|
|
|
|
diff --git a/libavcodec/mediacodec_wrapper.c b/libavcodec/mediacodec_wrapper.c
|
|
index 96c886666a..06b8504304 100644
|
|
--- a/libavcodec/mediacodec_wrapper.c
|
|
+++ b/libavcodec/mediacodec_wrapper.c
|
|
@@ -35,6 +35,8 @@
|
|
#include "ffjni.h"
|
|
#include "mediacodec_wrapper.h"
|
|
|
|
+#define PARAMETER_KEY_VIDEO_BITRATE "video-bitrate"
|
|
+
|
|
struct JNIAMediaCodecListFields {
|
|
|
|
jclass mediacodec_list_class;
|
|
@@ -195,6 +197,8 @@ struct JNIAMediaCodecFields {
|
|
jmethodID set_input_surface_id;
|
|
jmethodID signal_end_of_input_stream_id;
|
|
|
|
+ jmethodID set_parameters_id;
|
|
+
|
|
jclass mediainfo_class;
|
|
|
|
jmethodID init_id;
|
|
@@ -248,6 +252,8 @@ static const struct FFJniField jni_amediacodec_mapping[] = {
|
|
{ "android/media/MediaCodec", "setInputSurface", "(Landroid/view/Surface;)V", FF_JNI_METHOD, OFFSET(set_input_surface_id), 0 },
|
|
{ "android/media/MediaCodec", "signalEndOfInputStream", "()V", FF_JNI_METHOD, OFFSET(signal_end_of_input_stream_id), 0 },
|
|
|
|
+ { "android/media/MediaCodec", "setParameters", "(Landroid/os/Bundle;)V", FF_JNI_METHOD, OFFSET(set_parameters_id), 0 },
|
|
+
|
|
{ "android/media/MediaCodec$BufferInfo", NULL, NULL, FF_JNI_CLASS, OFFSET(mediainfo_class), 1 },
|
|
|
|
{ "android/media/MediaCodec.BufferInfo", "<init>", "()V", FF_JNI_METHOD, OFFSET(init_id), 1 },
|
|
@@ -292,6 +298,24 @@ typedef struct FFAMediaCodecJni {
|
|
|
|
static const FFAMediaCodec media_codec_jni;
|
|
|
|
+struct JNIABundleFields
|
|
+{
|
|
+ jclass bundle_class;
|
|
+ jmethodID init_id;
|
|
+ jmethodID put_int_id;
|
|
+};
|
|
+
|
|
+#define OFFSET(x) offsetof(struct JNIABundleFields, x)
|
|
+static const struct FFJniField jni_abundle_mapping[] = {
|
|
+ { "android/os/Bundle", NULL, NULL, FF_JNI_CLASS, OFFSET(bundle_class), 1 },
|
|
+
|
|
+ { "android/os/Bundle", "<init>", "()V", FF_JNI_METHOD, OFFSET(init_id), 1 },
|
|
+ { "android/os/Bundle", "putInt", "(Ljava/lang/String;I)V", FF_JNI_METHOD, OFFSET(put_int_id), 1 },
|
|
+
|
|
+ { NULL }
|
|
+};
|
|
+#undef OFFSET
|
|
+
|
|
#define JNI_GET_ENV_OR_RETURN(env, log_ctx, ret) do { \
|
|
(env) = ff_jni_get_env(log_ctx); \
|
|
if (!(env)) { \
|
|
@@ -1762,6 +1786,70 @@ static int mediacodec_jni_signalEndOfInputStream(FFAMediaCodec *ctx)
|
|
return 0;
|
|
}
|
|
|
|
+
|
|
+static int mediacodec_jni_setParameter(FFAMediaCodec *ctx, const char* name, int value)
|
|
+{
|
|
+ JNIEnv *env = NULL;
|
|
+ struct JNIABundleFields jfields = { 0 };
|
|
+ jobject object = NULL;
|
|
+ jstring key = NULL;
|
|
+ FFAMediaCodecJni *codec = (FFAMediaCodecJni *)ctx;
|
|
+ void *log_ctx = codec;
|
|
+ int ret = -1;
|
|
+
|
|
+ JNI_GET_ENV_OR_RETURN(env, codec, AVERROR_EXTERNAL);
|
|
+
|
|
+ if (ff_jni_init_jfields(env, &jfields, jni_abundle_mapping, 0, log_ctx) < 0) {
|
|
+ av_log(log_ctx, AV_LOG_ERROR, "Failed to init jfields\n");
|
|
+ goto fail;
|
|
+ }
|
|
+
|
|
+ object = (*env)->NewObject(env, jfields.bundle_class, jfields.init_id);
|
|
+ if (!object) {
|
|
+ av_log(log_ctx, AV_LOG_ERROR, "Failed to create bundle object\n");
|
|
+ goto fail;
|
|
+ }
|
|
+
|
|
+ key = ff_jni_utf_chars_to_jstring(env, name, log_ctx);
|
|
+ if (!key) {
|
|
+ av_log(log_ctx, AV_LOG_ERROR, "Failed to convert key to jstring\n");
|
|
+ goto fail;
|
|
+ }
|
|
+
|
|
+ (*env)->CallVoidMethod(env, object, jfields.put_int_id, key, value);
|
|
+ if (ff_jni_exception_check(env, 1, log_ctx) < 0) {
|
|
+ goto fail;
|
|
+ }
|
|
+
|
|
+ if (!codec->jfields.set_parameters_id) {
|
|
+ av_log(log_ctx, AV_LOG_ERROR, "System doesn't support setParameters\n");
|
|
+ goto fail;
|
|
+ }
|
|
+
|
|
+ (*env)->CallVoidMethod(env, codec->object, codec->jfields.set_parameters_id, object);
|
|
+ if (ff_jni_exception_check(env, 1, log_ctx) < 0) {
|
|
+ goto fail;
|
|
+ }
|
|
+
|
|
+ ret = 0;
|
|
+
|
|
+fail:
|
|
+ if (key) {
|
|
+ (*env)->DeleteLocalRef(env, key);
|
|
+ }
|
|
+ if (object) {
|
|
+ (*env)->DeleteLocalRef(env, object);
|
|
+ }
|
|
+ ff_jni_reset_jfields(env, &jfields, jni_abundle_mapping, 0, log_ctx);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int mediacodec_jni_setDynamicBitrate(FFAMediaCodec *ctx, int bitrate)
|
|
+{
|
|
+ return mediacodec_jni_setParameter(ctx, PARAMETER_KEY_VIDEO_BITRATE, bitrate);
|
|
+}
|
|
+
|
|
static const FFAMediaFormat media_format_jni = {
|
|
.class = &amediaformat_class,
|
|
|
|
@@ -1821,6 +1909,8 @@ static const FFAMediaCodec media_codec_jni = {
|
|
.getConfigureFlagEncode = mediacodec_jni_getConfigureFlagEncode,
|
|
.cleanOutputBuffers = mediacodec_jni_cleanOutputBuffers,
|
|
.signalEndOfInputStream = mediacodec_jni_signalEndOfInputStream,
|
|
+
|
|
+ .setDynamicBitrate = mediacodec_jni_setDynamicBitrate,
|
|
};
|
|
|
|
typedef struct FFAMediaFormatNdk {
|
|
@@ -2335,6 +2425,12 @@ static int mediacodec_ndk_signalEndOfInputStream(FFAMediaCodec *ctx)
|
|
return 0;
|
|
}
|
|
|
|
+static int mediacodec_ndk_setDynamicBitrate(FFAMediaCodec *ctx, int bitrate)
|
|
+{
|
|
+ av_log(ctx, AV_LOG_ERROR, "ndk setDynamicBitrate unavailable\n");
|
|
+ return -1;
|
|
+}
|
|
+
|
|
static const FFAMediaFormat media_format_ndk = {
|
|
.class = &amediaformat_ndk_class,
|
|
|
|
@@ -2396,6 +2492,8 @@ static const FFAMediaCodec media_codec_ndk = {
|
|
.getConfigureFlagEncode = mediacodec_ndk_getConfigureFlagEncode,
|
|
.cleanOutputBuffers = mediacodec_ndk_cleanOutputBuffers,
|
|
.signalEndOfInputStream = mediacodec_ndk_signalEndOfInputStream,
|
|
+
|
|
+ .setDynamicBitrate = mediacodec_ndk_setDynamicBitrate,
|
|
};
|
|
|
|
FFAMediaFormat *ff_AMediaFormat_new(int ndk)
|
|
diff --git a/libavcodec/mediacodec_wrapper.h b/libavcodec/mediacodec_wrapper.h
|
|
index 11a4260497..86c64556ad 100644
|
|
--- a/libavcodec/mediacodec_wrapper.h
|
|
+++ b/libavcodec/mediacodec_wrapper.h
|
|
@@ -219,6 +219,8 @@ struct FFAMediaCodec {
|
|
|
|
// For encoder with FFANativeWindow as input.
|
|
int (*signalEndOfInputStream)(FFAMediaCodec *);
|
|
+
|
|
+ int (*setDynamicBitrate)(FFAMediaCodec *codec, int bitrate);
|
|
};
|
|
|
|
static inline char *ff_AMediaCodec_getName(FFAMediaCodec *codec)
|
|
@@ -343,6 +345,11 @@ static inline int ff_AMediaCodec_signalEndOfInputStream(FFAMediaCodec *codec)
|
|
return codec->signalEndOfInputStream(codec);
|
|
}
|
|
|
|
+static inline int ff_AMediaCodec_setDynamicBitrate(FFAMediaCodec *codec, int bitrate)
|
|
+{
|
|
+ return codec->setDynamicBitrate(codec, bitrate);
|
|
+}
|
|
+
|
|
int ff_Build_SDK_INT(AVCodecContext *avctx);
|
|
|
|
enum FFAMediaFormatColorRange {
|
|
diff --git a/libavcodec/mediacodecenc.c b/libavcodec/mediacodecenc.c
|
|
index 6ca3968a24..221f7360f4 100644
|
|
--- a/libavcodec/mediacodecenc.c
|
|
+++ b/libavcodec/mediacodecenc.c
|
|
@@ -76,6 +76,8 @@ typedef struct MediaCodecEncContext {
|
|
int level;
|
|
int pts_as_dts;
|
|
int extract_extradata;
|
|
+
|
|
+ int last_bit_rate;
|
|
} MediaCodecEncContext;
|
|
|
|
enum {
|
|
@@ -193,6 +195,8 @@ static av_cold int mediacodec_init(AVCodecContext *avctx)
|
|
int ret;
|
|
int gop;
|
|
|
|
+ s->last_bit_rate = avctx->bit_rate;
|
|
+
|
|
if (s->use_ndk_codec < 0)
|
|
s->use_ndk_codec = !av_jni_get_java_vm(avctx);
|
|
|
|
@@ -542,11 +546,25 @@ static int mediacodec_send(AVCodecContext *avctx,
|
|
return 0;
|
|
}
|
|
|
|
+static void update_config(AVCodecContext *avctx)
|
|
+{
|
|
+ MediaCodecEncContext *s = avctx->priv_data;
|
|
+ if (avctx->bit_rate != s->last_bit_rate) {
|
|
+ s->last_bit_rate = avctx->bit_rate;
|
|
+ if (0 != ff_AMediaCodec_setDynamicBitrate(s->codec, avctx->bit_rate)) {
|
|
+ av_log(avctx, AV_LOG_ERROR, "Failed to set bitrate to %d\n", avctx->bit_rate);
|
|
+ } else {
|
|
+ av_log(avctx, AV_LOG_INFO, "Set bitrate to %d\n", avctx->bit_rate);
|
|
+ }
|
|
+ }
|
|
+}
|
|
+
|
|
static int mediacodec_encode(AVCodecContext *avctx, AVPacket *pkt)
|
|
{
|
|
MediaCodecEncContext *s = avctx->priv_data;
|
|
int ret;
|
|
|
|
+ update_config(avctx);
|
|
// Return on three case:
|
|
// 1. Serious error
|
|
// 2. Got a packet success
|
|
--
|
|
2.43.0.windows.1
|
|
|