This commit is contained in:
Yeicor 2026-04-19 22:07:51 +02:00 committed by GitHub
commit 464fa9d61f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 1383 additions and 5 deletions

View file

@ -31,6 +31,7 @@ src = [
'src/receiver.c',
'src/recorder.c',
'src/scrcpy.c',
'src/stream_sink.c',
'src/screen.c',
'src/server.c',
'src/version.c',

View file

@ -114,6 +114,7 @@ enum {
OPT_NO_VD_SYSTEM_DECORATIONS,
OPT_NO_VD_DESTROY_CONTENT,
OPT_DISPLAY_IME_POLICY,
OPT_STREAM_SINK,
};
struct sc_option {
@ -956,6 +957,17 @@ static const struct sc_option options[] = {
"Default is info.",
#endif
},
{
.longopt_id = OPT_STREAM_SINK,
.longopt = "stream-sink",
.argdesc = "url",
.text = "Stream the device video and audio as MPEG-TS to the given URL.\n"
"Supported protocols are srt, udp and tcp.\n"
"The URL is passed to the FFmpeg muxer, so it may contain "
"additional options (e.g. srt://HOST:PORT?latency=200).\n"
"For faster startup of clients, you may want to set "
"--video-codec-options=i-frame-interval:float=1.0."
},
{
.longopt_id = OPT_V4L2_SINK,
.longopt = "v4l2-sink",
@ -2686,6 +2698,9 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
LOGE("OTG mode (--otg) is disabled.");
return false;
#endif
case OPT_STREAM_SINK:
opts->stream_sink = optarg;
break;
case OPT_V4L2_SINK:
#ifdef HAVE_V4L2
opts->v4l2_device = optarg;
@ -2876,13 +2891,15 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
}
if (opts->video && !opts->video_playback && !opts->record_filename
&& !v4l2) {
LOGI("No video playback, no recording, no V4L2 sink: video disabled");
&& !v4l2 && !opts->stream_sink) {
LOGI("No video playback, no recording, no V4L2 sink, no stream sink: "
"video disabled");
opts->video = false;
}
if (opts->audio && !opts->audio_playback && !opts->record_filename) {
LOGI("No audio playback, no recording: audio disabled");
if (opts->audio && !opts->audio_playback && !opts->record_filename
&& !opts->stream_sink) {
LOGI("No audio playback, no recording, no stream sink: audio disabled");
opts->audio = false;
}

View file

@ -71,6 +71,7 @@ const struct scrcpy_options scrcpy_options_default = {
.v4l2_device = NULL,
.v4l2_buffer = 0,
#endif
.stream_sink = NULL,
#ifdef HAVE_USB
.otg = false,
#endif

View file

@ -281,6 +281,7 @@ struct scrcpy_options {
const char *v4l2_device;
sc_tick v4l2_buffer;
#endif
const char *stream_sink;
#ifdef HAVE_USB
bool otg;
#endif

View file

@ -26,6 +26,7 @@
#include "recorder.h"
#include "screen.h"
#include "server.h"
#include "stream_sink.h"
#include "uhid/gamepad_uhid.h"
#include "uhid/keyboard_uhid.h"
#include "uhid/mouse_uhid.h"
@ -54,6 +55,7 @@ struct scrcpy {
struct sc_decoder video_decoder;
struct sc_decoder audio_decoder;
struct sc_recorder recorder;
struct sc_stream_sink stream_sink;
struct sc_delay_buffer video_buffer;
#ifdef HAVE_V4L2
struct sc_v4l2_sink v4l2_sink;
@ -400,6 +402,8 @@ scrcpy(struct scrcpy_options *options) {
bool file_pusher_initialized = false;
bool recorder_initialized = false;
bool recorder_started = false;
bool stream_sink_initialized = false;
bool stream_sink_started = false;
#ifdef HAVE_V4L2
bool v4l2_sink_initialized = false;
#endif
@ -632,6 +636,28 @@ scrcpy(struct scrcpy_options *options) {
}
}
if (options->stream_sink) {
if (!sc_stream_sink_init(&s->stream_sink, options->stream_sink,
options->video, options->audio)) {
goto end;
}
stream_sink_initialized = true;
if (!sc_stream_sink_start(&s->stream_sink)) {
goto end;
}
stream_sink_started = true;
if (options->video) {
sc_packet_source_add_sink(&s->video_demuxer.packet_source,
&s->stream_sink.video_packet_sink);
}
if (options->audio) {
sc_packet_source_add_sink(&s->audio_demuxer.packet_source,
&s->stream_sink.audio_packet_sink);
}
}
struct sc_controller *controller = NULL;
struct sc_key_processor *kp = NULL;
struct sc_mouse_processor *mp = NULL;
@ -989,6 +1015,9 @@ end:
if (recorder_initialized) {
sc_recorder_stop(&s->recorder);
}
if (stream_sink_initialized) {
sc_stream_sink_stop(&s->stream_sink);
}
if (screen_initialized) {
sc_screen_interrupt(&s->screen);
}
@ -1053,6 +1082,13 @@ end:
sc_recorder_destroy(&s->recorder);
}
if (stream_sink_started) {
sc_stream_sink_join(&s->stream_sink);
}
if (stream_sink_initialized) {
sc_stream_sink_destroy(&s->stream_sink);
}
if (file_pusher_initialized) {
sc_file_pusher_join(&s->file_pusher);
sc_file_pusher_destroy(&s->file_pusher);

1229
app/src/stream_sink.c Normal file

File diff suppressed because it is too large Load diff

93
app/src/stream_sink.h Normal file
View file

@ -0,0 +1,93 @@
#ifndef SC_STREAM_SINK_H
#define SC_STREAM_SINK_H
#include "common.h"
#include <stdbool.h>
#include <stdatomic.h>
#include <libavcodec/packet.h>
#include <libavformat/avformat.h>
#include "trait/packet_sink.h"
#include "util/thread.h"
#include "util/vecdeque.h"
struct sc_stream_sink_queue SC_VECDEQUE(AVPacket *);
struct sc_stream_sink_stream {
int index;
int64_t last_pts;
};
/* Per-connection client state (defined in stream_sink.c). */
struct sc_stream_sink_client;
struct sc_stream_sink {
struct sc_packet_sink video_packet_sink;
struct sc_packet_sink audio_packet_sink;
/* The audio flag is unprotected:
* - it is initialized from sc_stream_sink_init() from the main thread;
* - it may be reset once from the stream sink thread if the audio is
* disabled dynamically.
*
* Therefore, once the stream sink thread is started, only the stream sink
* thread may access it without data races.
*/
bool audio;
bool video;
char *url;
// Template format context (no pb): holds stream definitions and codec
// parameters used to initialise a fresh context for each new connection.
AVFormatContext *ctx;
sc_thread thread;
sc_mutex mutex;
sc_cond cond;
// set on sc_stream_sink_stop(), packet_sink close or streaming failure
atomic_bool stopped;
// Init-phase queues: used only until template_ready is set.
// After that, each sc_stream_sink_client has its own queues.
struct sc_stream_sink_queue video_queue;
struct sc_stream_sink_queue audio_queue;
// wake up the stream sink thread once the video or audio codec is known
bool video_init;
bool audio_init;
bool audio_expects_config_packet;
// Stream indices shared by every per-client AVFormatContext (all clients
// copy the template streams in the same order).
struct sc_stream_sink_stream video_stream;
struct sc_stream_sink_stream audio_stream;
// Set to true once codec params + extradata are applied to the template
// context. Before this point packets are buffered in the init-phase queues
// above; after this point they are fanned out to active client queues.
bool template_ready;
// Linked list of currently active client connections (protected by mutex).
struct sc_stream_sink_client *clients;
};
bool
sc_stream_sink_init(struct sc_stream_sink *sink, const char *url,
bool video, bool audio);
bool
sc_stream_sink_start(struct sc_stream_sink *sink);
void
sc_stream_sink_stop(struct sc_stream_sink *sink);
void
sc_stream_sink_join(struct sc_stream_sink *sink);
void
sc_stream_sink_destroy(struct sc_stream_sink *sink);
#endif

View file

@ -7,7 +7,7 @@
#include "trait/packet_sink.h"
#define SC_PACKET_SOURCE_MAX_SINKS 2
#define SC_PACKET_SOURCE_MAX_SINKS 3
/**
* Packet source trait