From 96aded18ea14b62fc36728025370f7c7fd4f5e3b Mon Sep 17 00:00:00 2001 From: qiyinxi Date: Sun, 8 Nov 2026 01:09:13 +0100 Subject: [PATCH] =?UTF-8?q?=E6=8B=B7=E8=B4=9D=E9=80=94=E4=B8=AD=E6=AC=A1?= =?UTF-8?q?=E6=AC=A1=E6=9D=A5=E6=96=B0=E5=B8=A7=E6=97=B6=EF=BC=8Cmodavd=5F?= =?UTF-8?q?capture=20=E8=BF=94=E5=9B=9E=E6=9C=AA=E5=B0=B1=E7=BB=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 模拟器写共享内存不加锁,库拷前后比帧号,变了就重拷,最多 3 次。原来 3 次都撞上新帧也返回 MODAVD_OK,交出去的图可能是两帧拼起来的。现在 modavd_capture 返回 MODAVD_E_NOT_READY(并记一条 警告),调用方再截一次就行。出错码和接口版本不变,头文件里写明 NOT_READY 也包括这种情况。 - 兼容外壳照旧把最后一次拷贝交出去,和它替换的那个 DLL 一样:MaaCore 的 MuMu 那条路截图一失败就会 重新测速。所以外壳改为直接调 mx_copy_frame,认内部返回值 MX_TORN。 - 测试:加了只在测试里设置的钩子 mx_test_after_copy,在每次拷完、重读帧号之前发一帧,确定地造出 「拷贝途中来了新帧」,不靠多线程。 Refs ModAVD/ModAVDExtras#1 --- include/modavd_extras.h | 7 +++-- shim/mumu_shim.c | 9 +++++- src/core.c | 18 +++++++++-- src/mx_internal.h | 16 +++++++++- tests/test_core.c | 70 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 113 insertions(+), 7 deletions(-) diff --git a/include/modavd_extras.h b/include/modavd_extras.h index 7ff4359..80c5f49 100644 --- a/include/modavd_extras.h +++ b/include/modavd_extras.h @@ -46,7 +46,8 @@ enum { MODAVD_OK = 0, MODAVD_E_INVALID = -1, /* bad argument */ MODAVD_E_NOT_FOUND = -2, /* nothing answers on that console port */ - MODAVD_E_NOT_READY = -3, /* frame sharing is off, or no frame was composed yet */ + MODAVD_E_NOT_READY = -3, /* frame sharing is off, no frame was composed yet, or (capture) new frames + kept arriving during the copy: try again */ MODAVD_E_RESIZED = -4, /* the shared frame no longer fits the mapping: close and reopen */ MODAVD_E_BUFFER = -5, /* buffer too small; the frame info says how big it must be */ MODAVD_E_IO = -6, /* an OS call failed */ @@ -141,7 +142,9 @@ MODAVD_API int modavd_console_port(modavd_handle h); /* > 0, or MODAVD_E_IN /* Copies the latest frame of `display` (only 0 for now) into buf. * With buf == NULL or buf_size == 0 it only fills info (a size probe). - * MODAVD_E_NOT_READY: no frame yet. MODAVD_E_RESIZED: reopen the handle. + * MODAVD_E_NOT_READY: no frame yet, or the emulator published a new frame + * during each of the copies, so buf may mix two frames; + * call again. MODAVD_E_RESIZED: reopen the handle. * MODAVD_E_BUFFER: buf_size < info->stride * info->height. */ MODAVD_API int modavd_capture(modavd_handle h, int display, uint32_t format, uint32_t flags, void* buf, size_t buf_size, modavd_frame_info* info); diff --git a/shim/mumu_shim.c b/shim/mumu_shim.c index df95f05..419e9a8 100644 --- a/shim/mumu_shim.c +++ b/shim/mumu_shim.c @@ -203,7 +203,14 @@ EXPORT int nemu_capture_display(int handle, unsigned int display_id, int buffer_ memset(&info, 0, sizeof(info)); info.size = sizeof(info); size_t size = (buffer_size > 0 && pixels) ? (size_t)buffer_size : 0; - int rc = modavd_capture(h, 0, MODAVD_FMT_RGBA, MODAVD_CAPTURE_BOTTOM_UP, size ? pixels : NULL, size, &info); + /* mx_copy_frame, not modavd_capture: when a new frame arrived during every + * copy, hand the last copy over as the DLL this replaces did, instead of + * failing (MaaCore would drop to its screencap speed test again). */ + int rc = mx_copy_frame(&h->shm, MODAVD_FMT_RGBA, MODAVD_CAPTURE_BOTTOM_UP, h->simd, size ? pixels : NULL, size, + &info); + if (rc == MX_TORN) { + rc = MODAVD_OK; + } if (rc == MODAVD_OK || rc == MODAVD_E_BUFFER) { if (width) { *width = info.width; diff --git a/src/core.c b/src/core.c index 97e019e..27db02a 100644 --- a/src/core.c +++ b/src/core.c @@ -277,6 +277,9 @@ void mx_convert_row(const uint8_t* src, uint8_t* dst, uint32_t width, uint32_t f } } +void (*mx_test_after_copy)(void* user) = NULL; +void* mx_test_after_copy_user = NULL; + int mx_copy_frame(const mx_shm* shm, uint32_t format, uint32_t flags, int simd, void* buf, size_t buf_size, modavd_frame_info* info) { @@ -316,14 +319,17 @@ int mx_copy_frame(const mx_shm* shm, uint32_t format, uint32_t flags, int simd, uint32_t sy = (flags & MODAVD_CAPTURE_BOTTOM_UP) ? h - 1 - y : y; mx_convert_row(src + (size_t)sy * src_stride, dst + (size_t)y * (size_t)info->stride, w, format, simd); } + if (mx_test_after_copy) { + mx_test_after_copy(mx_test_after_copy_user); + } uint32_t after = vi->frame_number; info->frame_number = after; info->timestamp_us = vi->ts_us; if (after == before) { - break; + return MODAVD_OK; } } - return MODAVD_OK; + return MX_TORN; } int modavd_capture(modavd_handle h, int display, uint32_t format, uint32_t flags, void* buf, size_t buf_size, @@ -335,7 +341,13 @@ int modavd_capture(modavd_handle h, int display, uint32_t format, uint32_t flags if (display != 0) { return MODAVD_E_UNSUPPORTED; } - return mx_copy_frame(&h->shm, format, flags, h->simd, buf, buf_size, info); + int rc = mx_copy_frame(&h->shm, format, flags, h->simd, buf, buf_size, info); + if (rc == MX_TORN) { + mx_log(MODAVD_LOG_WARN, "port=%d: a new frame arrived during each of %d copies", h->console_port, + MX_MAX_COPY_ATTEMPTS); + return MODAVD_E_NOT_READY; + } + return rc; } /* Input arrives in the next version. */ diff --git a/src/mx_internal.h b/src/mx_internal.h index 867b82f..8e48383 100644 --- a/src/mx_internal.h +++ b/src/mx_internal.h @@ -31,6 +31,13 @@ typedef struct mx_video_info { #define MX_MAX_COPY_ATTEMPTS 3 #define MX_DEFAULT_TIMEOUT_MS 2000 +/* mx_copy_frame() when the emulator published a new frame during every one + * of the MX_MAX_COPY_ATTEMPTS copies: the buffer may mix two frames. Internal + * only (positive, never a MODAVD_* code): modavd_capture() reports + * MODAVD_E_NOT_READY; the MuMu-shaped shim hands the last copy over, as the + * DLL it replaces did. */ +#define MX_TORN 1 + /* ---- log.c ---- */ void mx_log(int level, const char* fmt, ...) #if defined(__GNUC__) @@ -75,10 +82,17 @@ int mx_cpu_simd(void); * simd: the value of mx_cpu_simd(), or 0 for the portable code only. */ void mx_convert_row(const uint8_t* src, uint8_t* dst, uint32_t width, uint32_t format, int simd); -/* Copies one frame; the work behind modavd_capture(). */ +/* Copies one frame; the work behind modavd_capture(). MODAVD_OK, a MODAVD_E_* + * code, or MX_TORN. */ int mx_copy_frame(const mx_shm* shm, uint32_t format, uint32_t flags, int simd, void* buf, size_t buf_size, modavd_frame_info* info); +/* Tests only: when set, mx_copy_frame() calls it after each copy, before it + * reads the frame number again, so a test can publish a frame mid-copy. NULL + * everywhere else. */ +extern void (*mx_test_after_copy)(void* user); +extern void* mx_test_after_copy_user; + #ifdef __cplusplus } #endif diff --git a/tests/test_core.c b/tests/test_core.c index fb0affc..c90013e 100644 --- a/tests/test_core.c +++ b/tests/test_core.c @@ -204,6 +204,75 @@ static void test_capture_from_fake_frame(void) fake_frame_destroy(&f); } +/* The emulator writes without a lock. The hook publishes a frame after a copy, + * as if the emulator wrote one meanwhile. */ +typedef struct publish_state { + fake_frame* frame; + int remaining; /* how many more copies get a new frame after them */ + int calls; +} publish_state; + +static void publish_after_copy(void* user) +{ + publish_state* s = (publish_state*)user; + ++s->calls; + if (s->remaining > 0) { + --s->remaining; + ++((mx_video_info*)s->frame->view)->frame_number; + } +} + +/* One new frame mid-copy: the next copy is clean. A new frame during every + * copy: MODAVD_E_NOT_READY rather than a frame that may mix two. */ +static void test_capture_new_frame_mid_copy(void) +{ + const int port = 65020; + const uint32_t w = 8, hgt = 4; + fake_frame f; + if (fake_frame_create(&f, port, w, hgt, w, hgt) != 0) { + fprintf(stderr, "cannot create the fake frame\n"); + ++g_failures; + return; + } + modavd_open_params p; + memset(&p, 0, sizeof(p)); + p.size = sizeof(p); + p.console_port = port; + p.flags = MODAVD_OPEN_NO_START; + modavd_handle h = NULL; + CHECK_EQ(modavd_open(&p, &h), MODAVD_OK); + if (h) { + uint8_t buf[8 * 4 * 4]; + modavd_frame_info info; + memset(&info, 0, sizeof(info)); + info.size = sizeof(info); + uint32_t first = ((mx_video_info*)f.view)->frame_number; + publish_state s = { &f, 1, 0 }; + mx_test_after_copy = publish_after_copy; + mx_test_after_copy_user = &s; + + CHECK_EQ(modavd_capture(h, 0, MODAVD_FMT_BGRA, 0, buf, sizeof(buf), &info), MODAVD_OK); + CHECK_EQ(s.calls, 2); + CHECK_EQ(info.frame_number, first + 1); + + s.remaining = MX_MAX_COPY_ATTEMPTS; + s.calls = 0; + CHECK_EQ(modavd_capture(h, 0, MODAVD_FMT_BGRA, 0, buf, sizeof(buf), &info), MODAVD_E_NOT_READY); + CHECK_EQ(s.calls, MX_MAX_COPY_ATTEMPTS); + + /* The shim's call: it hands the last copy over, so it needs to tell this apart. */ + s.remaining = MX_MAX_COPY_ATTEMPTS; + s.calls = 0; + CHECK_EQ(mx_copy_frame(&h->shm, MODAVD_FMT_RGBA, MODAVD_CAPTURE_BOTTOM_UP, 0, buf, sizeof(buf), &info), + MX_TORN); + + mx_test_after_copy = NULL; + mx_test_after_copy_user = NULL; + modavd_close(h); + } + fake_frame_destroy(&f); +} + /* Same frame through the public call at sizes that leave every possible tail after the * 16- and 4-pixel blocks, top-down and bottom-up. */ static void test_capture_odd_sizes(void) @@ -347,6 +416,7 @@ int main(void) test_version_and_errors(); test_open_errors(); test_capture_from_fake_frame(); + test_capture_new_frame_mid_copy(); test_capture_odd_sizes(); test_convert_row(); test_log_callback();