Signal client resize in session packets

So far, scrcpy always resized the window whenever a frame with a new
size was received. However, with the upcoming "flex display" feature,
the size of the virtual display can change either on its own (e.g.,
due to app rotation) or as a result of a client window resize.

Track the cause of each resize and signal it in the session metadata.

Resize the window only if the change was not triggered by a client
request to prevent resize loops and stuttering.
This commit is contained in:
Romain Vimont 2026-04-16 19:40:08 +02:00
parent 160a378719
commit 3b068b669e
7 changed files with 61 additions and 24 deletions

View file

@ -76,10 +76,10 @@ sc_demuxer_recv_header(struct sc_demuxer *demuxer,
// which only contains a 12-byte header:
//
// byte 0 byte 1 byte 2 byte 3
// 10000000 00000000 00000000 00000000
// ^<-------------------------------->
// | padding
// `- session packet flag
// 10000000 00000000 00000000 0000000.
// ^<------------------------------->^
// | padding |
// `- session packet flag `- client resized flag
//
// byte 4 byte 5 byte 6 byte 7 byte 8 byte 9 byte 10 byte 11
// ........ ........ ........ ........ ........ ........ ........ ........
@ -126,6 +126,7 @@ sc_demuxer_parse_session(const uint8_t *header,
assert(sc_demuxer_is_session(header));
session->video.width = sc_read32be(&header[4]);
session->video.height = sc_read32be(&header[8]);
session->video.client_resized = header[3] & 1;
}
static bool

View file

@ -329,6 +329,8 @@ sc_screen_frame_sink_open(struct sc_frame_sink *sink,
screen->content_size = get_oriented_size(screen->frame_size,
screen->orientation);
screen->current_session = *session;
bool ok = sc_push_event(SC_EVENT_OPEN_WINDOW);
if (!ok) {
return false;
@ -361,6 +363,7 @@ sc_screen_frame_sink_push(struct sc_frame_sink *sink, const AVFrame *frame) {
sc_mutex_lock(&screen->mutex);
bool previous_skipped = sc_frame_buffer_has_frame(&screen->fb);
bool ok = sc_frame_buffer_push(&screen->fb, frame);
screen->prevent_auto_resize = screen->current_session.video.client_resized;
sc_mutex_unlock(&screen->mutex);
if (!ok) {
return false;
@ -381,6 +384,14 @@ sc_screen_frame_sink_push(struct sc_frame_sink *sink, const AVFrame *frame) {
return true;
}
static bool
sc_screen_frame_sink_push_session(struct sc_frame_sink *sink,
const struct sc_stream_session *session) {
struct sc_screen *screen = DOWNCAST(sink);
screen->current_session = *session;
return true;
}
bool
sc_screen_init(struct sc_screen *screen,
const struct sc_screen_params *params) {
@ -403,6 +414,8 @@ sc_screen_init(struct sc_screen *screen,
screen->req.fullscreen = params->fullscreen;
screen->req.start_fps_counter = params->start_fps_counter;
screen->prevent_auto_resize = false;
bool ok = sc_mutex_init(&screen->mutex);
if (!ok) {
return false;
@ -570,10 +583,13 @@ sc_screen_init(struct sc_screen *screen,
}
#endif
memset(&screen->current_session, 0, sizeof(screen->current_session));
static const struct sc_frame_sink_ops ops = {
.open = sc_screen_frame_sink_open,
.close = sc_screen_frame_sink_close,
.push = sc_screen_frame_sink_push,
.push_session = sc_screen_frame_sink_push_session,
};
screen->frame_sink.ops = &ops;
@ -725,16 +741,19 @@ resize_for_content(struct sc_screen *screen, struct sc_size old_content_size,
}
static void
set_content_size(struct sc_screen *screen, struct sc_size new_content_size) {
set_content_size(struct sc_screen *screen, struct sc_size new_content_size,
bool resize) {
assert(screen->video);
if (is_windowed(screen)) {
resize_for_content(screen, screen->content_size, new_content_size);
} else if (!screen->resize_pending) {
// Store the windowed size to be able to compute the optimal size once
// fullscreen/maximized/minimized are disabled
screen->windowed_content_size = screen->content_size;
screen->resize_pending = true;
if (resize) {
if (is_windowed(screen)) {
resize_for_content(screen, screen->content_size, new_content_size);
} else if (!screen->resize_pending) {
// Store the windowed size to be able to compute the optimal size
// once fullscreen/maximized/minimized are disabled
screen->windowed_content_size = screen->content_size;
screen->resize_pending = true;
}
}
screen->content_size = new_content_size;
@ -764,7 +783,7 @@ sc_screen_set_orientation(struct sc_screen *screen,
struct sc_size new_content_size =
get_oriented_size(screen->frame_size, orientation);
set_content_size(screen, new_content_size);
set_content_size(screen, new_content_size, true);
screen->orientation = orientation;
LOGI("Display orientation set to %s", sc_orientation_get_name(orientation));
@ -773,7 +792,7 @@ sc_screen_set_orientation(struct sc_screen *screen,
}
static bool
sc_screen_apply_frame(struct sc_screen *screen) {
sc_screen_apply_frame(struct sc_screen *screen, bool can_resize) {
assert(screen->video);
assert(screen->window_shown);
@ -790,7 +809,7 @@ sc_screen_apply_frame(struct sc_screen *screen) {
struct sc_size new_content_size =
get_oriented_size(new_frame_size, screen->orientation);
set_content_size(screen, new_content_size);
set_content_size(screen, new_content_size, can_resize);
sc_screen_update_content_rect(screen);
}
@ -826,8 +845,10 @@ sc_screen_update_frame(struct sc_screen *screen) {
av_frame_unref(screen->frame);
sc_mutex_lock(&screen->mutex);
sc_frame_buffer_consume(&screen->fb, screen->frame);
// read with lock held
bool can_resize = !screen->prevent_auto_resize;
sc_mutex_unlock(&screen->mutex);
return sc_screen_apply_frame(screen);
return sc_screen_apply_frame(screen, can_resize);
}
void
@ -845,7 +866,7 @@ sc_screen_set_paused(struct sc_screen *screen, bool paused) {
av_frame_free(&screen->frame);
screen->frame = screen->resume_frame;
screen->resume_frame = NULL;
bool ok = sc_screen_apply_frame(screen);
bool ok = sc_screen_apply_frame(screen, true);
if (!ok) {
LOGE("Resume frame update failed");
}

View file

@ -46,6 +46,8 @@ struct sc_screen {
struct sc_mutex mutex;
struct sc_frame_buffer fb; // protected by mutex
// When true, a frame size change must not cause the window to be resized
bool prevent_auto_resize; // protected by mutex
// The initial requested window properties
struct {
@ -77,6 +79,9 @@ struct sc_screen {
struct SDL_FRect rect;
bool window_shown;
// only accessed from the thread calling sc_frame_sink_ops functions
struct sc_stream_session current_session;
AVFrame *frame;
bool paused;

View file

@ -18,6 +18,7 @@ struct sc_packet_sink {
struct sc_stream_session_video {
uint32_t width;
uint32_t height;
bool client_resized;
};
struct sc_stream_session {

View file

@ -370,10 +370,10 @@ session (a session changes when the device rotates):
```
byte 0 byte 1 byte 2 byte 3
10000000 00000000 00000000 00000000
^<-------------------------------->
| padding
`- session packet flag
10000000 00000000 00000000 0000000.
^<------------------------------->^
| padding |
`- session packet flag `- client resized flag
byte 4 byte 5 byte 6 byte 7 byte 8 byte 9 byte 10 byte 11
........ ........ ........ ........ ........ ........ ........ ........
@ -381,6 +381,11 @@ session (a session changes when the device rotates):
video width video height
```
The "client resized" flag is used for _flex displays_ to indicate that the frame
size changed due to a client resize request (see [#6772]).
[#6772]: https://github.com/Genymobile/scrcpy/pull/6772
For the _audio_ stream, there are no _session packets_.
Then _media packets_ are sent, each containing the payload produced by

View file

@ -88,11 +88,15 @@ public final class Streamer {
writePacket(codecBuffer, pts, config, keyFrame);
}
public void writeSessionMeta(int width, int height) throws IOException {
public void writeSessionMeta(int width, int height, boolean clientResize) throws IOException {
if (sendStreamMeta) {
headerBuffer.clear();
headerBuffer.putInt((int) (PACKET_FLAG_SESSION >> 32)); // Set the first bit to 1
int flags = (int) (PACKET_FLAG_SESSION >> 32); // set the first bit to 1
if (clientResize) {
flags |= 1;
}
headerBuffer.putInt(flags);
headerBuffer.putInt(width);
headerBuffer.putInt(height);
headerBuffer.flip();

View file

@ -128,7 +128,7 @@ public class SurfaceEncoder implements AsyncProcessor {
} else {
if (!captureControl.isResetRequested()) {
// If a reset is requested during encode(), it will interrupt the encoding by an EOS
streamer.writeSessionMeta(size.getWidth(), size.getHeight());
streamer.writeSessionMeta(size.getWidth(), size.getHeight(), false);
encode(mediaCodec, streamer);
}