Compare commits

..

10 Commits

Author SHA1 Message Date
Romain Vimont
9562bd7ee2 fix 2025-10-07 20:34:29 +02:00
dddddjent
7ba1785183 Fix SDL colorspace matching AVCOL_SPC_RGB
Use BT.709 color space for AVCOL_SPC_RGB.

Refs #1868 comment <https://github.com/Genymobile/scrcpy/issues/1868#issuecomment-3293917796>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2025-10-07 20:08:00 +02:00
Romain Vimont
c59835a0e9 Build SDL3 for test step on Github Actions
The latest Ubuntu does not provide the SDL3 package yet.
2025-10-07 20:08:00 +02:00
Romain Vimont
aea678a8df Upgrade SDL build script for SDL3 2025-10-07 20:08:00 +02:00
Romain Vimont
6bb2415d13 Migrate from SDL2 to SDL3
Refs <https://wiki.libsdl.org/SDL3/README-migration>
2025-10-07 20:08:00 +02:00
Romain Vimont
70557fbe83 Improve color space conversion
Use both the color space and color range from FFmpeg to determine the
appropriate SDL YUV conversion mode.
2025-10-07 20:08:00 +02:00
Romain Vimont
018a6513dc Set color range during texture creation
This prepares for the migration to SDL3, where the color range can only
be specified at the time of texture creation.
2025-10-07 20:08:00 +02:00
Romain Vimont
34bacd9758 Create texture on first frame
The texture was created as soon as the initial video size was known,
even before the first frame arrived.

However, texture creation will require other data, such as the color
range, which is only available once the first frame is received.

Therefore, delay texture creation until the first frame.
2025-10-07 20:08:00 +02:00
Romain Vimont
d36aa75591 Remove prepare_for_frame()
Inline the function body in its only caller.

This will simplify further refactors.
2025-10-07 20:08:00 +02:00
Romain Vimont
3ffe258264 Handle mouse integer scrolling locally
Do not rely on SDL to expose integer scroll values, as they are not
available in all versions.

It was reimplemented in SDL 3.2.12 by this commit:
<0447c2f3c3>

Refs #6156 <https://github.com/Genymobile/scrcpy/issues/6156>
2025-10-07 20:08:00 +02:00
27 changed files with 208 additions and 506 deletions

View File

@@ -47,10 +47,7 @@ sc_audio_player_frame_sink_push(struct sc_frame_sink *sink,
static bool
sc_audio_player_frame_sink_open(struct sc_frame_sink *sink,
const AVCodecContext *ctx,
const struct sc_stream_session *session) {
(void) session;
const AVCodecContext *ctx) {
struct sc_audio_player *ap = DOWNCAST(sink);
#ifdef SCRCPY_LAVU_HAS_CHLAYOUT

View File

@@ -10,30 +10,20 @@
#define DOWNCAST(SINK) container_of(SINK, struct sc_decoder, packet_sink)
static bool
sc_decoder_open(struct sc_decoder *decoder, AVCodecContext *ctx,
const struct sc_stream_session *session) {
sc_decoder_open(struct sc_decoder *decoder, AVCodecContext *ctx) {
decoder->frame = av_frame_alloc();
if (!decoder->frame) {
LOG_OOM();
return false;
}
if (!sc_frame_source_sinks_open(&decoder->frame_source, ctx, session)) {
if (!sc_frame_source_sinks_open(&decoder->frame_source, ctx)) {
av_frame_free(&decoder->frame);
return false;
}
decoder->ctx = ctx;
// A video stream must have a session
assert(session || ctx->codec_type != AVMEDIA_TYPE_VIDEO);
if (session) {
decoder->session = *session;
}
memset(&decoder->frame_size, 0, sizeof(decoder->frame_size));
return true;
}
@@ -71,32 +61,6 @@ sc_decoder_push(struct sc_decoder *decoder, const AVPacket *packet) {
}
// a frame was received
if (decoder->ctx->codec_type == AVMEDIA_TYPE_VIDEO) {
assert(decoder->frame->width >= 0);
assert(decoder->frame->height >= 0);
struct sc_size frame_size = {
.width = decoder->frame->width,
.height = decoder->frame->height,
};
if (decoder->frame_size.width != frame_size.width
|| decoder->frame_size.height != frame_size.height) {
// The frame size has changed, check if it matches the session
uint32_t sw = decoder->session.video.width;
uint32_t sh = decoder->session.video.height;
if (frame_size.width != sw || frame_size.height != sh) {
LOGW("Unexpected video size: %" PRIu32 "x%" PRIu32
" (expected %" PRIu32 "x%" PRIu32 ")",
frame_size.width, frame_size.height, sw, sh);
LOGW("The encoder did not respect the requested size, "
"please retry with a lower resolution (-m/--max-size)");
}
}
decoder->frame_size = frame_size;
}
bool ok = sc_frame_source_sinks_push(&decoder->frame_source,
decoder->frame);
av_frame_unref(decoder->frame);
@@ -110,17 +74,9 @@ sc_decoder_push(struct sc_decoder *decoder, const AVPacket *packet) {
}
static bool
sc_decoder_push_session(struct sc_decoder *decoder,
const struct sc_stream_session *session) {
decoder->session = *session;
return sc_frame_source_sinks_push_session(&decoder->frame_source, session);
}
static bool
sc_decoder_packet_sink_open(struct sc_packet_sink *sink, AVCodecContext *ctx,
const struct sc_stream_session *session) {
sc_decoder_packet_sink_open(struct sc_packet_sink *sink, AVCodecContext *ctx) {
struct sc_decoder *decoder = DOWNCAST(sink);
return sc_decoder_open(decoder, ctx, session);
return sc_decoder_open(decoder, ctx);
}
static void
@@ -136,14 +92,6 @@ sc_decoder_packet_sink_push(struct sc_packet_sink *sink,
return sc_decoder_push(decoder, packet);
}
static bool
sc_decoder_packet_sink_push_session(struct sc_packet_sink *sink,
const struct sc_stream_session *session) {
struct sc_decoder *decoder = DOWNCAST(sink);
return sc_decoder_push_session(decoder, session);
}
void
sc_decoder_init(struct sc_decoder *decoder, const char *name) {
decoder->name = name; // statically allocated
@@ -153,7 +101,6 @@ sc_decoder_init(struct sc_decoder *decoder, const char *name) {
.open = sc_decoder_packet_sink_open,
.close = sc_decoder_packet_sink_close,
.push = sc_decoder_packet_sink_push,
.push_session = sc_decoder_packet_sink_push_session,
};
decoder->packet_sink.ops = &ops;

View File

@@ -5,7 +5,6 @@
#include <libavcodec/avcodec.h>
#include "coords.h"
#include "trait/frame_source.h"
#include "trait/packet_sink.h"
@@ -17,9 +16,6 @@ struct sc_decoder {
AVCodecContext *ctx;
AVFrame *frame;
struct sc_stream_session session; // only initialized for video stream
struct sc_size frame_size;
};
// The name must be statically allocated (e.g. a string literal)

View File

@@ -10,18 +10,16 @@
#define DOWNCAST(SINK) container_of(SINK, struct sc_delay_buffer, frame_sink)
static bool
sc_delayed_packet_init_frame(struct sc_delayed_packet *dpacket,
const AVFrame *frame) {
dpacket->type = SC_DELAYED_PACKET_TYPE_FRAME;
dpacket->frame = av_frame_alloc();
if (!dpacket->frame) {
sc_delayed_frame_init(struct sc_delayed_frame *dframe, const AVFrame *frame) {
dframe->frame = av_frame_alloc();
if (!dframe->frame) {
LOG_OOM();
return false;
}
if (av_frame_ref(dpacket->frame, frame)) {
if (av_frame_ref(dframe->frame, frame)) {
LOG_OOM();
av_frame_free(&dpacket->frame);
av_frame_free(&dframe->frame);
return false;
}
@@ -29,18 +27,9 @@ sc_delayed_packet_init_frame(struct sc_delayed_packet *dpacket,
}
static void
sc_delayed_packet_init_session(struct sc_delayed_packet *dpacket,
const struct sc_stream_session *session) {
dpacket->type = SC_DELAYED_PACKET_TYPE_SESSION;
dpacket->session = *session;
}
static void
sc_delayed_packet_destroy(struct sc_delayed_packet *dpacket) {
if (dpacket->type == SC_DELAYED_PACKET_TYPE_FRAME) {
av_frame_unref(dpacket->frame);
av_frame_free(&dpacket->frame);
}
sc_delayed_frame_destroy(struct sc_delayed_frame *dframe) {
av_frame_unref(dframe->frame);
av_frame_free(&dframe->frame);
}
static int
@@ -61,52 +50,43 @@ run_buffering(void *data) {
goto stopped;
}
struct sc_delayed_packet dpacket = sc_vecdeque_pop(&db->queue);
struct sc_delayed_frame dframe = sc_vecdeque_pop(&db->queue);
bool ok;
if (dpacket.type == SC_DELAYED_PACKET_TYPE_FRAME) {
sc_tick max_deadline = sc_tick_now() + db->delay;
// PTS (written by the server) are expressed in microseconds
sc_tick pts = SC_TICK_FROM_US(dpacket.frame->pts);
sc_tick max_deadline = sc_tick_now() + db->delay;
// PTS (written by the server) are expressed in microseconds
sc_tick pts = SC_TICK_FROM_US(dframe.frame->pts);
bool timed_out = false;
while (!db->stopped && !timed_out) {
sc_tick deadline = sc_clock_to_system_time(&db->clock, pts)
+ db->delay;
if (deadline > max_deadline) {
deadline = max_deadline;
}
timed_out =
!sc_cond_timedwait(&db->wait_cond, &db->mutex, deadline);
bool timed_out = false;
while (!db->stopped && !timed_out) {
sc_tick deadline = sc_clock_to_system_time(&db->clock, pts)
+ db->delay;
if (deadline > max_deadline) {
deadline = max_deadline;
}
bool stopped = db->stopped;
sc_mutex_unlock(&db->mutex);
if (stopped) {
sc_delayed_packet_destroy(&dpacket);
goto stopped;
}
#ifdef SC_BUFFERING_DEBUG
LOGD("Buffering: %" PRItick ";%" PRItick ";%" PRItick,
pts, dframe.push_date, sc_tick_now());
#endif
ok = sc_frame_source_sinks_push(&db->frame_source, dpacket.frame);
} else {
assert(dpacket.type == SC_DELAYED_PACKET_TYPE_SESSION);
sc_mutex_unlock(&db->mutex);
ok = sc_frame_source_sinks_push_session(&db->frame_source,
&dpacket.session);
timed_out =
!sc_cond_timedwait(&db->wait_cond, &db->mutex, deadline);
}
sc_delayed_packet_destroy(&dpacket);
bool stopped = db->stopped;
sc_mutex_unlock(&db->mutex);
if (stopped) {
sc_delayed_frame_destroy(&dframe);
goto stopped;
}
#ifdef SC_BUFFERING_DEBUG
LOGD("Buffering: %" PRItick ";%" PRItick ";%" PRItick,
pts, dframe.push_date, sc_tick_now());
#endif
bool ok = sc_frame_source_sinks_push(&db->frame_source, dframe.frame);
sc_delayed_frame_destroy(&dframe);
if (!ok) {
LOGE("Delayed packet could not be pushed, stopping");
LOGE("Delayed frame could not be pushed, stopping");
sc_mutex_lock(&db->mutex);
// Prevent to push any new packet
// Prevent to push any new frame
db->stopped = true;
sc_mutex_unlock(&db->mutex);
goto stopped;
@@ -118,8 +98,8 @@ stopped:
// Flush queue
while (!sc_vecdeque_is_empty(&db->queue)) {
struct sc_delayed_packet *dpacket = sc_vecdeque_popref(&db->queue);
sc_delayed_packet_destroy(dpacket);
struct sc_delayed_frame *dframe = sc_vecdeque_popref(&db->queue);
sc_delayed_frame_destroy(dframe);
}
LOGD("Buffering thread ended");
@@ -129,11 +109,9 @@ stopped:
static bool
sc_delay_buffer_frame_sink_open(struct sc_frame_sink *sink,
const AVCodecContext *ctx,
const struct sc_stream_session *session) {
const AVCodecContext *ctx) {
struct sc_delay_buffer *db = DOWNCAST(sink);
(void) ctx;
(void) session;
bool ok = sc_mutex_init(&db->mutex);
if (!ok) {
@@ -154,7 +132,7 @@ sc_delay_buffer_frame_sink_open(struct sc_frame_sink *sink,
sc_vecdeque_init(&db->queue);
db->stopped = false;
if (!sc_frame_source_sinks_open(&db->frame_source, ctx, session)) {
if (!sc_frame_source_sinks_open(&db->frame_source, ctx)) {
goto error_destroy_wait_cond;
}
@@ -218,56 +196,24 @@ sc_delay_buffer_frame_sink_push(struct sc_frame_sink *sink,
return sc_frame_source_sinks_push(&db->frame_source, frame);
}
struct sc_delayed_packet *dpacket = sc_vecdeque_push_hole(&db->queue);
if (!dpacket) {
struct sc_delayed_frame dframe;
bool ok = sc_delayed_frame_init(&dframe, frame);
if (!ok) {
sc_mutex_unlock(&db->mutex);
LOG_OOM();
return false;
}
bool ok = sc_delayed_packet_init_frame(dpacket, frame);
#ifdef SC_BUFFERING_DEBUG
dframe.push_date = sc_tick_now();
#endif
ok = sc_vecdeque_push(&db->queue, dframe);
if (!ok) {
sc_mutex_unlock(&db->mutex);
LOG_OOM();
return false;
}
#ifdef SC_BUFFERING_DEBUG
dpacket->push_date = sc_tick_now();
#endif
sc_cond_signal(&db->queue_cond);
sc_mutex_unlock(&db->mutex);
return true;
}
static bool
sc_delay_buffer_frame_sink_push_session(struct sc_frame_sink *sink,
const struct sc_stream_session *session) {
struct sc_delay_buffer *db = DOWNCAST(sink);
sc_mutex_lock(&db->mutex);
if (db->stopped) {
sc_mutex_unlock(&db->mutex);
return false;
}
struct sc_delayed_packet *dpacket = sc_vecdeque_push_hole(&db->queue);
if (!dpacket) {
sc_mutex_unlock(&db->mutex);
LOG_OOM();
return false;
}
sc_delayed_packet_init_session(dpacket, session);
#ifdef SC_BUFFERING_DEBUG
dpacket->push_date = sc_tick_now();
#endif
sc_cond_signal(&db->queue_cond);
sc_mutex_unlock(&db->mutex);
@@ -289,7 +235,6 @@ sc_delay_buffer_init(struct sc_delay_buffer *db, sc_tick delay,
.open = sc_delay_buffer_frame_sink_open,
.close = sc_delay_buffer_frame_sink_close,
.push = sc_delay_buffer_frame_sink_push,
.push_session = sc_delay_buffer_frame_sink_push_session,
};
db->frame_sink.ops = &ops;

View File

@@ -18,23 +18,14 @@
// forward declarations
typedef struct AVFrame AVFrame;
enum sc_delayed_packet_type {
SC_DELAYED_PACKET_TYPE_FRAME,
SC_DELAYED_PACKET_TYPE_SESSION,
};
struct sc_delayed_packet {
enum sc_delayed_packet_type type;
union {
AVFrame *frame;
struct sc_stream_session session;
};
struct sc_delayed_frame {
AVFrame *frame;
#ifdef SC_BUFFERING_DEBUG
sc_tick push_date;
#endif
};
struct sc_delayed_packet_queue SC_VECDEQUE(struct sc_delayed_packet);
struct sc_delayed_frame_queue SC_VECDEQUE(struct sc_delayed_frame);
struct sc_delay_buffer {
struct sc_frame_source frame_source; // frame source trait
@@ -49,7 +40,7 @@ struct sc_delay_buffer {
sc_cond wait_cond;
struct sc_clock clock;
struct sc_delayed_packet_queue queue;
struct sc_delayed_frame_queue queue;
bool stopped;
};

View File

@@ -11,8 +11,8 @@
#define SC_PACKET_HEADER_SIZE 12
#define SC_PACKET_FLAG_CONFIG (UINT64_C(1) << 62)
#define SC_PACKET_FLAG_KEY_FRAME (UINT64_C(1) << 61)
#define SC_PACKET_FLAG_CONFIG (UINT64_C(1) << 63)
#define SC_PACKET_FLAG_KEY_FRAME (UINT64_C(1) << 62)
#define SC_PACKET_PTS_MASK (SC_PACKET_FLAG_KEY_FRAME - 1)
@@ -63,75 +63,48 @@ sc_demuxer_recv_codec_id(struct sc_demuxer *demuxer, uint32_t *codec_id) {
return true;
}
static inline bool
sc_demuxer_recv_header(struct sc_demuxer *demuxer,
uint8_t buf[static SC_PACKET_HEADER_SIZE]) {
static bool
sc_demuxer_recv_video_size(struct sc_demuxer *demuxer, uint32_t *width,
uint32_t *height) {
uint8_t data[8];
ssize_t r = net_recv_all(demuxer->socket, data, 8);
if (r < 8) {
return false;
}
*width = sc_read32be(data);
*height = sc_read32be(data + 4);
return true;
}
static bool
sc_demuxer_recv_packet(struct sc_demuxer *demuxer, AVPacket *packet) {
// The video and audio streams contain a sequence of raw packets (as
// provided by MediaCodec), each prefixed with a "meta" header.
//
// The "meta" header length is 12 bytes.
//
//
// If the MSB is 1, then it is a session packet (for a video stream only),
// which only contains a 12-byte header:
//
// byte 0 byte 1 byte 2 byte 3
// 10000000 00000000 00000000 00000000
// ^<-------------------------------->
// | padding
// `- session packet flag
//
// byte 4 byte 5 byte 6 byte 7 byte 8 byte 9 byte 10 byte 11
// ........ ........ ........ ........ ........ ........ ........ ........
// <---------------------------------> <--------------------------------->
// video width video height
//
//
// If the MSB is 0, then it is a media packet, comprised of a 12-byte header
// followed by <packet_size> bytes containing the packet/frame:
//
// The "meta" header length is 12 bytes:
// [. . . . . . . .|. . . .]. . . . . . . . . . . . . . . ...
// <-------------> <-----> <-----------------------------...
// PTS packet raw packet
// size
//
// It is followed by <packet_size> bytes containing the packet/frame.
//
// The most significant bits of the PTS are used for packet flags:
//
// byte 0 byte 1 byte 2 byte 3 byte 4 byte 5 byte 6 byte 7
// 0CK..... ........ ........ ........ ........ ........ ........ ........
// ^^^<------------------------------------------------------------------>
// ||| PTS
// || `- key frame
// | `-- config packet
// `--- media packet flag
//
// byte 8 byte 9 byte 10 byte 11
// ........ ........ ........ ........ ........ ........ . . .
// <---------------------------------> <---------------- . . .
// packet size raw packet
//
ssize_t r = net_recv_all(demuxer->socket, buf, SC_PACKET_HEADER_SIZE);
assert(r <= SC_PACKET_HEADER_SIZE);
return r == SC_PACKET_HEADER_SIZE;
}
// byte 7 byte 6 byte 5 byte 4 byte 3 byte 2 byte 1 byte 0
// CK...... ........ ........ ........ ........ ........ ........ ........
// ^^<------------------------------------------------------------------->
// || PTS
// | `- key frame
// `-- config packet
static bool
sc_demuxer_is_session(const uint8_t *header) {
return header[0] & 0x80;
}
uint8_t header[SC_PACKET_HEADER_SIZE];
ssize_t r = net_recv_all(demuxer->socket, header, SC_PACKET_HEADER_SIZE);
if (r < SC_PACKET_HEADER_SIZE) {
return false;
}
static void
sc_demuxer_parse_session(const uint8_t *header,
struct sc_stream_session *session) {
assert(sc_demuxer_is_session(header));
session->video.width = sc_read32be(&header[4]);
session->video.height = sc_read32be(&header[8]);
}
static bool
sc_demuxer_recv_packet(struct sc_demuxer *demuxer, const uint8_t *header,
AVPacket *packet) {
assert(!sc_demuxer_is_session(header));
uint64_t pts_flags = sc_read64be(header);
uint32_t len = sc_read32be(&header[8]);
assert(len);
@@ -141,7 +114,7 @@ sc_demuxer_recv_packet(struct sc_demuxer *demuxer, const uint8_t *header,
return false;
}
ssize_t r = net_recv_all(demuxer->socket, packet->data, len);
r = net_recv_all(demuxer->socket, packet->data, len);
if (r < 0 || ((uint32_t) r) < len) {
av_packet_unref(packet);
return false;
@@ -214,28 +187,17 @@ run_demuxer(void *data) {
codec_ctx->flags |= AV_CODEC_FLAG_LOW_DELAY;
uint8_t header[SC_PACKET_HEADER_SIZE];
struct sc_stream_session session_data;
struct sc_stream_session *session = NULL;
if (codec->type == AVMEDIA_TYPE_VIDEO) {
bool ok = sc_demuxer_recv_header(demuxer, header);
uint32_t width;
uint32_t height;
ok = sc_demuxer_recv_video_size(demuxer, &width, &height);
if (!ok) {
goto finally_free_context;
}
if (!sc_demuxer_is_session(header)) {
LOGE("Unexpected packet (not a session header)");
goto finally_free_context;
}
session = &session_data;
sc_demuxer_parse_session(header, session);
codec_ctx->width = session_data.video.width;
codec_ctx->height = session_data.video.height;
codec_ctx->width = width;
codec_ctx->height = height;
codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
} else {
// Hardcoded audio properties
#ifdef SCRCPY_LAVU_HAS_CHLAYOUT
@@ -257,8 +219,7 @@ run_demuxer(void *data) {
goto finally_free_context;
}
if (!sc_packet_source_sinks_open(&demuxer->packet_source, codec_ctx,
session)) {
if (!sc_packet_source_sinks_open(&demuxer->packet_source, codec_ctx)) {
goto finally_free_context;
}
@@ -280,39 +241,27 @@ run_demuxer(void *data) {
}
for (;;) {
bool ok = sc_demuxer_recv_header(demuxer, header);
bool ok = sc_demuxer_recv_packet(demuxer, packet);
if (!ok) {
// end of stream
status = SC_DEMUXER_STATUS_EOS;
break;
}
if (sc_demuxer_is_session(header)) {
sc_demuxer_parse_session(header, &session_data);
ok = sc_packet_source_sinks_push_session(&demuxer->packet_source,
&session_data);
if (must_merge_config_packet) {
// Prepend any config packet to the next media packet
ok = sc_packet_merger_merge(&merger, packet);
if (!ok) {
// The sink already logged its concrete error
av_packet_unref(packet);
break;
}
} else {
sc_demuxer_recv_packet(demuxer, header, packet);
}
if (must_merge_config_packet) {
// Prepend any config packet to the next media packet
ok = sc_packet_merger_merge(&merger, packet);
if (!ok) {
av_packet_unref(packet);
break;
}
}
ok = sc_packet_source_sinks_push(&demuxer->packet_source, packet);
av_packet_unref(packet);
if (!ok) {
// The sink already logged its concrete error
break;
}
ok = sc_packet_source_sinks_push(&demuxer->packet_source, packet);
av_packet_unref(packet);
if (!ok) {
// The sink already logged its concrete error
break;
}
}

View File

@@ -53,7 +53,7 @@ sc_device_msg_deserialize(const uint8_t *buf, size_t len,
}
uint16_t id = sc_read16be(&buf[1]);
size_t size = sc_read16be(&buf[3]);
if (size > len - 5) {
if (size < len - 5) {
return 0; // not available
}
uint8_t *data = malloc(size);

View File

@@ -374,9 +374,7 @@ sc_display_update_texture(struct sc_display *display, const AVFrame *frame) {
enum sc_display_result
sc_display_render(struct sc_display *display, const SDL_Rect *geometry,
enum sc_orientation orientation) {
bool always_ok = SDL_RenderClear(display->renderer);
(void) always_ok;
assert(always_ok);
SDL_RenderClear(display->renderer);
if (display->pending.flags) {
bool ok = sc_display_apply_pending(display);
@@ -421,8 +419,11 @@ sc_display_render(struct sc_display *display, const SDL_Rect *geometry,
}
}
always_ok = SDL_RenderPresent(display->renderer);
assert(always_ok);
bool ok = SDL_RenderPresent(display->renderer);
if (!ok) {
LOGE("Could not render the content");
return SC_DISPLAY_RESULT_ERROR;
}
return SC_DISPLAY_RESULT_OK;
}

View File

@@ -674,9 +674,7 @@ sc_input_manager_process_touch(struct sc_input_manager *im,
int dw;
int dh;
bool always_ok = SDL_GetWindowSizeInPixels(im->screen->window, &dw, &dh);
(void) always_ok;
assert(always_ok);
SDL_GetWindowSizeInPixels(im->screen->window, &dw, &dh);
// SDL touch event coordinates are normalized in the range [0; 1]
int32_t x = event->x * dw;
@@ -898,8 +896,13 @@ sc_input_manager_process_mouse_wheel(struct sc_input_manager *im,
struct sc_mouse_scroll_event evt = {
.position = sc_input_manager_get_position(im, mouse_x, mouse_y),
#if SDL_VERSION_ATLEAST(2, 0, 18)
.hscroll = event->x,
.vscroll = event->y,
#else
.hscroll = event->x,
.vscroll = event->y,
#endif
.buttons_state = im->mouse_buttons_state,
};

View File

@@ -81,6 +81,24 @@ sc_mouse_capture_handle_event(struct sc_mouse_capture *mc,
void
sc_mouse_capture_set_active(struct sc_mouse_capture *mc, bool capture) {
#ifdef __APPLE__
// Workaround for SDL bug on macOS:
// <https://github.com/libsdl-org/SDL/issues/5340>
if (capture) {
float mouse_x, mouse_y;
SDL_GetGlobalMouseState(&mouse_x, &mouse_y);
int x, y, w, h;
SDL_GetWindowPosition(mc->window, &x, &y);
SDL_GetWindowSize(mc->window, &w, &h);
bool outside_window = mouse_x < x || mouse_x >= x + w
|| mouse_y < y || mouse_y >= y + h;
if (outside_window) {
SDL_WarpMouseInWindow(mc->window, w / 2, h / 2);
}
}
#endif
bool ok = SDL_SetWindowRelativeMouseMode(mc->window, capture);
if (!ok) {
LOGE("Could not set relative mouse mode to %s: %s",

View File

@@ -54,12 +54,8 @@ task_set_clipboard(void *userdata) {
if (same) {
LOGD("Computer clipboard unchanged");
} else {
bool ok = SDL_SetClipboardText(text);
if (ok) {
LOGI("Device clipboard copied");
} else {
LOGE("Could not set clipboard: %s", SDL_GetError());
}
LOGI("Device clipboard copied");
SDL_SetClipboardText(text);
}
free(text);

View File

@@ -541,10 +541,7 @@ sc_recorder_set_orientation(AVStream *stream, enum sc_orientation orientation) {
static bool
sc_recorder_video_packet_sink_open(struct sc_packet_sink *sink,
AVCodecContext *ctx,
const struct sc_stream_session *session) {
(void) session;
AVCodecContext *ctx) {
struct sc_recorder *recorder = DOWNCAST_VIDEO(sink);
// only written from this thread, no need to lock
assert(!recorder->video_init);
@@ -638,10 +635,7 @@ sc_recorder_video_packet_sink_push(struct sc_packet_sink *sink,
static bool
sc_recorder_audio_packet_sink_open(struct sc_packet_sink *sink,
AVCodecContext *ctx,
const struct sc_stream_session *session) {
(void) session;
AVCodecContext *ctx) {
struct sc_recorder *recorder = DOWNCAST_AUDIO(sink);
assert(recorder->audio);
// only written from this thread, no need to lock

View File

@@ -154,15 +154,9 @@ sdl_configure(bool video_playback, bool disable_screensaver) {
}
if (disable_screensaver) {
bool ok = SDL_DisableScreenSaver();
if (!ok) {
LOGW("Could not disable screen saver");
}
SDL_DisableScreenSaver();
} else {
bool ok = SDL_EnableScreenSaver();
if (!ok) {
LOGW("Could not enable screen saver");
}
SDL_EnableScreenSaver();
}
}

View File

@@ -32,9 +32,7 @@ static struct sc_size
get_window_size(const struct sc_screen *screen) {
int width;
int height;
bool always_ok = SDL_GetWindowSize(screen->window, &width, &height);
(void) always_ok;
assert(always_ok);
SDL_GetWindowSize(screen->window, &width, &height);
struct sc_size size;
size.width = width;
@@ -46,9 +44,7 @@ static struct sc_point
get_window_position(const struct sc_screen *screen) {
int x;
int y;
bool always_ok = SDL_GetWindowPosition(screen->window, &x, &y);
(void) always_ok;
assert(always_ok);
SDL_GetWindowPosition(screen->window, &x, &y);
struct sc_point point;
point.x = x;
@@ -62,10 +58,7 @@ set_window_size(struct sc_screen *screen, struct sc_size new_size) {
assert(!screen->fullscreen);
assert(!screen->maximized);
assert(!screen->minimized);
bool always_ok =
SDL_SetWindowSize(screen->window, new_size.width, new_size.height);
(void) always_ok;
assert(always_ok);
SDL_SetWindowSize(screen->window, new_size.width, new_size.height);
}
// get the preferred display bounds (i.e. the screen bounds with some margins)
@@ -178,9 +171,7 @@ sc_screen_update_content_rect(struct sc_screen *screen) {
int dw;
int dh;
bool always_ok = SDL_GetWindowSizeInPixels(screen->window, &dw, &dh);
(void) always_ok;
assert(always_ok);
SDL_GetWindowSizeInPixels(screen->window, &dw, &dh);
struct sc_size content_size = screen->content_size;
// The drawable size is the window size * the HiDPI scale
@@ -265,11 +256,9 @@ event_watcher(void *data, SDL_Event *event) {
static bool
sc_screen_frame_sink_open(struct sc_frame_sink *sink,
const AVCodecContext *ctx,
const struct sc_stream_session *session) {
const AVCodecContext *ctx) {
assert(ctx->pix_fmt == AV_PIX_FMT_YUV420P);
(void) ctx;
(void) session;
struct sc_screen *screen = DOWNCAST(sink);
(void) screen;
@@ -462,11 +451,7 @@ sc_screen_init(struct sc_screen *screen,
#ifdef CONTINUOUS_RESIZING_WORKAROUND
if (screen->video) {
ok = SDL_AddEventWatch(event_watcher, screen);
if (!ok) {
LOGW("Could not add event watcher for continuous resizing: %s",
SDL_GetError());
}
SDL_AddEventWatch(event_watcher, screen);
}
#endif
@@ -513,12 +498,7 @@ sc_screen_show_initial_window(struct sc_screen *screen) {
screen->req.height);
set_window_size(screen, window_size);
bool always_ok;
(void) always_ok;
always_ok = SDL_SetWindowPosition(screen->window, x, y);
assert(always_ok);
SDL_SetWindowPosition(screen->window, x, y);
if (screen->req.fullscreen) {
sc_screen_toggle_fullscreen(screen);
@@ -528,17 +508,13 @@ sc_screen_show_initial_window(struct sc_screen *screen) {
sc_fps_counter_start(&screen->fps_counter);
}
always_ok = SDL_ShowWindow(screen->window);
assert(always_ok);
SDL_ShowWindow(screen->window);
sc_screen_update_content_rect(screen);
}
void
sc_screen_hide_window(struct sc_screen *screen) {
bool always_ok = SDL_HideWindow(screen->window);
(void) always_ok;
assert(always_ok);
SDL_HideWindow(screen->window);
}
void
@@ -784,16 +760,8 @@ sc_screen_resize_to_fit(struct sc_screen *screen) {
uint32_t new_x = point.x + (window_size.width - optimal_size.width) / 2;
uint32_t new_y = point.y + (window_size.height - optimal_size.height) / 2;
bool always_ok;
(void) always_ok;
always_ok = SDL_SetWindowSize(screen->window, optimal_size.width,
optimal_size.height);
assert(always_ok);
always_ok = SDL_SetWindowPosition(screen->window, new_x, new_y);
assert(always_ok);
SDL_SetWindowSize(screen->window, optimal_size.width, optimal_size.height);
SDL_SetWindowPosition(screen->window, new_x, new_y);
LOGD("Resized to optimal size: %ux%u", optimal_size.width,
optimal_size.height);
}
@@ -806,20 +774,13 @@ sc_screen_resize_to_pixel_perfect(struct sc_screen *screen) {
return;
}
bool always_ok;
(void) always_ok;
if (screen->maximized) {
always_ok = SDL_RestoreWindow(screen->window);
assert(always_ok);
SDL_RestoreWindow(screen->window);
screen->maximized = false;
}
struct sc_size content_size = screen->content_size;
always_ok = SDL_SetWindowSize(screen->window, content_size.width,
content_size.height);
assert(always_ok);
SDL_SetWindowSize(screen->window, content_size.width, content_size.height);
LOGD("Resized to pixel-perfect: %ux%u", content_size.width,
content_size.height);
}
@@ -948,16 +909,10 @@ sc_screen_convert_window_to_frame_coords(struct sc_screen *screen,
void
sc_screen_hidpi_scale_coords(struct sc_screen *screen, int32_t *x, int32_t *y) {
bool always_ok;
(void) always_ok;
// take the HiDPI scaling (dw/ww and dh/wh) into account
int ww, wh, dw, dh;
always_ok = SDL_GetWindowSize(screen->window, &ww, &wh);
assert(always_ok);
always_ok = SDL_GetWindowSizeInPixels(screen->window, &dw, &dh);
assert(always_ok);
SDL_GetWindowSize(screen->window, &ww, &wh);
SDL_GetWindowSizeInPixels(screen->window, &dw, &dh);
// scale for HiDPI (64 bits for intermediate multiplications)
*x = (int64_t) *x * dw / ww;

View File

@@ -6,8 +6,6 @@
#include <stdbool.h>
#include <libavcodec/avcodec.h>
#include "trait/packet_sink.h"
/**
* Frame sink trait.
*
@@ -19,16 +17,9 @@ struct sc_frame_sink {
struct sc_frame_sink_ops {
/* The codec context is valid until the sink is closed */
bool (*open)(struct sc_frame_sink *sink, const AVCodecContext *ctx,
const struct sc_stream_session *session);
bool (*open)(struct sc_frame_sink *sink, const AVCodecContext *ctx);
void (*close)(struct sc_frame_sink *sink);
bool (*push)(struct sc_frame_sink *sink, const AVFrame *frame);
/**
* Optional callback to be notified of a new stream session.
*/
bool (*push_session)(struct sc_frame_sink *sink,
const struct sc_stream_session *session);
};
#endif

View File

@@ -27,12 +27,11 @@ sc_frame_source_sinks_close_firsts(struct sc_frame_source *source,
bool
sc_frame_source_sinks_open(struct sc_frame_source *source,
const AVCodecContext *ctx,
const struct sc_stream_session *session) {
const AVCodecContext *ctx) {
assert(source->sink_count);
for (unsigned i = 0; i < source->sink_count; ++i) {
struct sc_frame_sink *sink = source->sinks[i];
if (!sink->ops->open(sink, ctx, session)) {
if (!sink->ops->open(sink, ctx)) {
sc_frame_source_sinks_close_firsts(source, i);
return false;
}
@@ -60,18 +59,3 @@ sc_frame_source_sinks_push(struct sc_frame_source *source,
return true;
}
bool
sc_frame_source_sinks_push_session(struct sc_frame_source *source,
const struct sc_stream_session *session) {
assert(source->sink_count);
for (unsigned i = 0; i < source->sink_count; ++i) {
struct sc_frame_sink *sink = source->sinks[i];
if (sink->ops->push_session &&
!sink->ops->push_session(sink, session)) {
return false;
}
}
return true;
}

View File

@@ -28,8 +28,7 @@ sc_frame_source_add_sink(struct sc_frame_source *source,
bool
sc_frame_source_sinks_open(struct sc_frame_source *source,
const AVCodecContext *ctx,
const struct sc_stream_session *session);
const AVCodecContext *ctx);
void
sc_frame_source_sinks_close(struct sc_frame_source *source);
@@ -38,8 +37,4 @@ bool
sc_frame_source_sinks_push(struct sc_frame_source *source,
const AVFrame *frame);
bool
sc_frame_source_sinks_push_session(struct sc_frame_source *source,
const struct sc_stream_session *session);
#endif

View File

@@ -15,28 +15,12 @@ struct sc_packet_sink {
const struct sc_packet_sink_ops *ops;
};
struct sc_stream_session_video {
uint32_t width;
uint32_t height;
};
struct sc_stream_session {
struct sc_stream_session_video video;
};
struct sc_packet_sink_ops {
/* The codec context is valid until the sink is closed */
bool (*open)(struct sc_packet_sink *sink, AVCodecContext *ctx,
const struct sc_stream_session *session);
bool (*open)(struct sc_packet_sink *sink, AVCodecContext *ctx);
void (*close)(struct sc_packet_sink *sink);
bool (*push)(struct sc_packet_sink *sink, const AVPacket *packet);
/**
* Optional callback to be notified of a new stream session.
*/
bool (*push_session)(struct sc_packet_sink *sink,
const struct sc_stream_session *session);
/*/
* Called when the input stream has been disabled at runtime.
*

View File

@@ -27,12 +27,11 @@ sc_packet_source_sinks_close_firsts(struct sc_packet_source *source,
bool
sc_packet_source_sinks_open(struct sc_packet_source *source,
AVCodecContext *ctx,
const struct sc_stream_session *session) {
AVCodecContext *ctx) {
assert(source->sink_count);
for (unsigned i = 0; i < source->sink_count; ++i) {
struct sc_packet_sink *sink = source->sinks[i];
if (!sink->ops->open(sink, ctx, session)) {
if (!sink->ops->open(sink, ctx)) {
sc_packet_source_sinks_close_firsts(source, i);
return false;
}
@@ -61,20 +60,6 @@ sc_packet_source_sinks_push(struct sc_packet_source *source,
return true;
}
bool
sc_packet_source_sinks_push_session(struct sc_packet_source *source,
const struct sc_stream_session *session) {
assert(source->sink_count);
for (unsigned i = 0; i < source->sink_count; ++i) {
struct sc_packet_sink *sink = source->sinks[i];
if (!sink->ops->push_session(sink, session)) {
return false;
}
}
return true;
}
void
sc_packet_source_sinks_disable(struct sc_packet_source *source) {
assert(source->sink_count);

View File

@@ -28,8 +28,7 @@ sc_packet_source_add_sink(struct sc_packet_source *source,
bool
sc_packet_source_sinks_open(struct sc_packet_source *source,
AVCodecContext *ctx,
const struct sc_stream_session *session);
AVCodecContext *ctx);
void
sc_packet_source_sinks_close(struct sc_packet_source *source);
@@ -38,10 +37,6 @@ bool
sc_packet_source_sinks_push(struct sc_packet_source *source,
const AVPacket *packet);
bool
sc_packet_source_sinks_push_session(struct sc_packet_source *source,
const struct sc_stream_session *session);
void
sc_packet_source_sinks_disable(struct sc_packet_source *source);

View File

@@ -11,20 +11,11 @@
static void
sc_screen_otg_render(struct sc_screen_otg *screen) {
bool always_ok = SDL_RenderClear(screen->renderer);
(void) always_ok;
assert(always_ok);
SDL_RenderClear(screen->renderer);
if (screen->texture) {
bool ok =
SDL_RenderTexture(screen->renderer, screen->texture, NULL, NULL);
if (!ok) {
LOGW("Could not render texture: %s", SDL_GetError());
}
SDL_RenderTexture(screen->renderer, screen->texture, NULL, NULL);
}
always_ok = SDL_RenderPresent(screen->renderer);
assert(always_ok);
SDL_RenderPresent(screen->renderer);
}
bool
@@ -68,13 +59,10 @@ sc_screen_otg_init(struct sc_screen_otg *screen,
SDL_Surface *icon = scrcpy_icon_load();
if (icon) {
bool ok = SDL_SetWindowIcon(screen->window, icon);
if (!ok) {
LOGW("Could not set window icon: %s", SDL_GetError());
}
SDL_SetWindowIcon(screen->window, icon);
ok = SDL_SetRenderLogicalPresentation(screen->renderer, icon->w,
icon->h,
bool ok =
SDL_SetRenderLogicalPresentation(screen->renderer, icon->w, icon->h,
SDL_LOGICAL_PRESENTATION_LETTERBOX);
if (!ok) {
LOGW("Could not set renderer logical size: %s", SDL_GetError());
@@ -181,8 +169,13 @@ sc_screen_otg_process_mouse_wheel(struct sc_screen_otg *screen,
struct sc_mouse_scroll_event evt = {
// .position not used for HID events
#if SDL_VERSION_ATLEAST(2, 0, 18)
.hscroll = event->x,
.vscroll = event->y,
#else
.hscroll = event->x,
.vscroll = event->y,
#endif
.buttons_state = sc_mouse_buttons_state_from_sdl(sdl_buttons_state),
};

View File

@@ -146,11 +146,9 @@ run_v4l2_sink(void *data) {
}
static bool
sc_v4l2_sink_open(struct sc_v4l2_sink *vs, const AVCodecContext *ctx,
const struct sc_stream_session *session) {
sc_v4l2_sink_open(struct sc_v4l2_sink *vs, const AVCodecContext *ctx) {
assert(ctx->pix_fmt == AV_PIX_FMT_YUV420P);
(void) ctx;
(void) session;
bool ok = sc_frame_buffer_init(&vs->fb);
if (!ok) {
@@ -328,10 +326,9 @@ sc_v4l2_sink_push(struct sc_v4l2_sink *vs, const AVFrame *frame) {
}
static bool
sc_v4l2_frame_sink_open(struct sc_frame_sink *sink, const AVCodecContext *ctx,
const struct sc_stream_session *session) {
sc_v4l2_frame_sink_open(struct sc_frame_sink *sink, const AVCodecContext *ctx) {
struct sc_v4l2_sink *vs = DOWNCAST(sink);
return sc_v4l2_sink_open(vs, ctx, session);
return sc_v4l2_sink_open(vs, ctx);
}
static void

View File

@@ -409,11 +409,12 @@ with any client which uses the same protocol.
For simplicity, some [server-specific options] have been added to produce raw
streams easily:
- `send_device_meta=false`: disable device metadata (in practice, the device
- `send_device_meta=false`: disable the device metata (in practice, the device
name) sent on the _first_ socket
- `send_frame_meta=false`: disable the 12-byte header for each packet
- `send_dummy_byte`: disable the dummy byte sent on forward connections
- `send_stream_meta`: disable codec and video size metadata
- `send_codec_meta`: disable the codec information (and initial device size for
video)
- `raw_stream`: disable all the above
[server-specific options]: https://github.com/Genymobile/scrcpy/blob/a3cdf1a6b86ea22786e1f7d09b9c202feabc6949/server/src/main/java/com/genymobile/scrcpy/Options.java#L309-L329

View File

@@ -78,7 +78,7 @@ public class Options {
private boolean sendDeviceMeta = true; // send device name and size
private boolean sendFrameMeta = true; // send PTS so that the client may record properly
private boolean sendDummyByte = true; // write a byte on start to detect connection issues
private boolean sendStreamMeta = true; // write the stream metadata (codec and session)
private boolean sendCodecMeta = true; // write the codec metadata before the stream
public Ln.Level getLogLevel() {
return logLevel;
@@ -284,8 +284,8 @@ public class Options {
return sendDummyByte;
}
public boolean getSendStreamMeta() {
return sendStreamMeta;
public boolean getSendCodecMeta() {
return sendCodecMeta;
}
@SuppressWarnings("MethodLength")
@@ -500,8 +500,8 @@ public class Options {
case "send_dummy_byte":
options.sendDummyByte = Boolean.parseBoolean(value);
break;
case "send_stream_meta":
options.sendStreamMeta = Boolean.parseBoolean(value);
case "send_codec_meta":
options.sendCodecMeta = Boolean.parseBoolean(value);
break;
case "raw_stream":
boolean rawStream = Boolean.parseBoolean(value);
@@ -509,7 +509,7 @@ public class Options {
options.sendDeviceMeta = false;
options.sendFrameMeta = false;
options.sendDummyByte = false;
options.sendStreamMeta = false;
options.sendCodecMeta = false;
}
break;
default:

View File

@@ -125,7 +125,7 @@ public final class Server {
audioCapture = new AudioPlaybackCapture(options.getAudioDup());
}
Streamer audioStreamer = new Streamer(connection.getAudioFd(), audioCodec, options.getSendStreamMeta(), options.getSendFrameMeta());
Streamer audioStreamer = new Streamer(connection.getAudioFd(), audioCodec, options.getSendCodecMeta(), options.getSendFrameMeta());
AsyncProcessor audioRecorder;
if (audioCodec == AudioCodec.RAW) {
audioRecorder = new AudioRawRecorder(audioCapture, audioStreamer);
@@ -136,7 +136,7 @@ public final class Server {
}
if (video) {
Streamer videoStreamer = new Streamer(connection.getVideoFd(), options.getVideoCodec(), options.getSendStreamMeta(),
Streamer videoStreamer = new Streamer(connection.getVideoFd(), options.getVideoCodec(), options.getSendCodecMeta(),
options.getSendFrameMeta());
SurfaceCapture surfaceCapture;
if (options.getVideoSource() == VideoSource.DISPLAY) {

View File

@@ -14,13 +14,12 @@ import java.util.Arrays;
public final class Streamer {
private static final long PACKET_FLAG_SESSION = 1L << 63;
private static final long PACKET_FLAG_CONFIG = 1L << 62;
private static final long PACKET_FLAG_KEY_FRAME = 1L << 61;
private static final long PACKET_FLAG_CONFIG = 1L << 63;
private static final long PACKET_FLAG_KEY_FRAME = 1L << 62;
private final FileDescriptor fd;
private final Codec codec;
private final boolean sendStreamMeta;
private final boolean sendCodecMeta;
private final boolean sendFrameMeta;
private final ByteBuffer headerBuffer = ByteBuffer.allocate(12);
@@ -28,7 +27,7 @@ public final class Streamer {
public Streamer(FileDescriptor fd, Codec codec, boolean sendCodecMeta, boolean sendFrameMeta) {
this.fd = fd;
this.codec = codec;
this.sendStreamMeta = sendCodecMeta;
this.sendCodecMeta = sendCodecMeta;
this.sendFrameMeta = sendFrameMeta;
}
@@ -37,7 +36,7 @@ public final class Streamer {
}
public void writeAudioHeader() throws IOException {
if (sendStreamMeta) {
if (sendCodecMeta) {
ByteBuffer buffer = ByteBuffer.allocate(4);
buffer.putInt(codec.getId());
buffer.flip();
@@ -45,10 +44,12 @@ public final class Streamer {
}
}
public void writeVideoHeader() throws IOException {
if (sendStreamMeta) {
ByteBuffer buffer = ByteBuffer.allocate(4);
public void writeVideoHeader(Size videoSize) throws IOException {
if (sendCodecMeta) {
ByteBuffer buffer = ByteBuffer.allocate(12);
buffer.putInt(codec.getId());
buffer.putInt(videoSize.getWidth());
buffer.putInt(videoSize.getHeight());
buffer.flip();
IO.writeFully(fd, buffer);
}
@@ -88,18 +89,6 @@ public final class Streamer {
writePacket(codecBuffer, pts, config, keyFrame);
}
public void writeSessionMeta(int width, int height) throws IOException {
if (sendStreamMeta) {
headerBuffer.clear();
headerBuffer.putInt((int) (PACKET_FLAG_SESSION >> 32)); // Set the first bit to 1
headerBuffer.putInt(width);
headerBuffer.putInt(height);
headerBuffer.flip();
IO.writeFully(fd, headerBuffer);
}
}
private void writeFrameMeta(FileDescriptor fd, int packetSize, long pts, boolean config, boolean keyFrame) throws IOException {
headerBuffer.clear();

View File

@@ -71,13 +71,16 @@ public class SurfaceEncoder implements AsyncProcessor {
try {
boolean alive;
streamer.writeVideoHeader();
boolean headerWritten = false;
do {
reset.consumeReset(); // If a capture reset was requested, it is implicitly fulfilled
capture.prepare();
Size size = capture.getSize();
if (!headerWritten) {
streamer.writeVideoHeader(size);
headerWritten = true;
}
format.setInteger(MediaFormat.KEY_WIDTH, size.getWidth());
format.setInteger(MediaFormat.KEY_HEIGHT, size.getHeight());
@@ -104,7 +107,6 @@ public class SurfaceEncoder implements AsyncProcessor {
boolean resetRequested = reset.consumeReset();
if (!resetRequested) {
// If a reset is requested during encode(), it will interrupt the encoding by an EOS
streamer.writeSessionMeta(size.getWidth(), size.getHeight());
encode(mediaCodec, streamer);
}
// The capture might have been closed internally (for example if the camera is disconnected)