From 8c650e53cd37a53d5c3aa746c30a71c6b742a4e2 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sun, 7 May 2023 12:08:50 +0200 Subject: [PATCH] Add --no-video Similar to --no-audio, add --no-video to play audio only. Fixes #3842 PR #3978 --- app/data/bash-completion/scrcpy | 1 + app/data/zsh-completion/_scrcpy | 1 + app/scrcpy.1 | 4 + app/src/cli.c | 21 ++++- app/src/options.c | 1 + app/src/options.h | 1 + app/src/recorder.c | 69 ++++++++++------ app/src/recorder.h | 3 +- app/src/scrcpy.c | 54 ++++++++----- app/src/server.c | 80 ++++++++++++------- app/src/server.h | 1 + doc/audio.md | 15 ++++ doc/recording.md | 6 +- doc/video.md | 10 +++ .../genymobile/scrcpy/DesktopConnection.java | 49 +++++++++--- .../java/com/genymobile/scrcpy/Options.java | 8 ++ .../java/com/genymobile/scrcpy/Server.java | 15 ++-- 17 files changed, 243 insertions(+), 96 deletions(-) diff --git a/app/data/bash-completion/scrcpy b/app/data/bash-completion/scrcpy index a0fca23d..cdc9270f 100644 --- a/app/data/bash-completion/scrcpy +++ b/app/data/bash-completion/scrcpy @@ -37,6 +37,7 @@ _scrcpy() { --no-key-repeat --no-mipmaps --no-power-on + --no-video --otg -p --port= --power-off-on-close diff --git a/app/data/zsh-completion/_scrcpy b/app/data/zsh-completion/_scrcpy index ccb51a2c..5e40b2fb 100644 --- a/app/data/zsh-completion/_scrcpy +++ b/app/data/zsh-completion/_scrcpy @@ -43,6 +43,7 @@ arguments=( '--no-key-repeat[Do not forward repeated key events when a key is held down]' '--no-mipmaps[Disable the generation of mipmaps]' '--no-power-on[Do not power on the device on start]' + '--no-video[Disable video forwarding]' '--otg[Run in OTG mode \(simulating physical keyboard and mouse\)]' {-p,--port=}'[\[port\[\:port\]\] Set the TCP port \(range\) used by the client to listen]' '--power-off-on-close[Turn the device screen off when closing scrcpy]' diff --git a/app/scrcpy.1 b/app/scrcpy.1 index 6ef01680..29d14b58 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -225,6 +225,10 @@ If the renderer is OpenGL 3.0+ or OpenGL ES 2.0+, then mipmaps are automatically .B \-\-no\-power\-on Do not power on the device on start. +.TP +.B \-\-no\-video +Disable video forwarding. + .TP .B \-\-otg Run in OTG mode: simulate physical keyboard and mouse, as if the computer keyboard and mouse were plugged directly to the device via an OTG cable. diff --git a/app/src/cli.c b/app/src/cli.c index 066f46fc..1558ef0c 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -73,6 +73,7 @@ enum { OPT_AUDIO_BUFFER, OPT_AUDIO_OUTPUT_BUFFER, OPT_NO_DISPLAY, + OPT_NO_VIDEO, }; struct sc_option { @@ -407,6 +408,11 @@ static const struct sc_option options[] = { .longopt = "no-power-on", .text = "Do not power on the device on start.", }, + { + .longopt_id = OPT_NO_VIDEO, + .longopt = "no-video", + .text = "Disable video forwarding.", + }, { .longopt_id = OPT_OTG, .longopt = "otg", @@ -1797,6 +1803,9 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], case OPT_NO_DOWNSIZE_ON_ERROR: opts->downsize_on_error = false; break; + case OPT_NO_VIDEO: + opts->video = false; + break; case OPT_NO_AUDIO: opts->audio = false; break; @@ -2042,14 +2051,20 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], #endif #ifdef HAVE_USB - if (!opts->mirror && opts->control && !opts->otg) { + if (!(opts->mirror && opts->video) && !opts->otg) { #else - if (!opts->mirror && opts->control) { + if (!(opts->mirror && opts->video)) { #endif - LOGD("Mirroring is disabled, force --no-control"); + // If video mirroring is disabled and OTG are disabled, then there is + // no way to control the device. opts->control = false; } + if (!opts->video) { + // If video is disabled, then scrcpy must exit on audio failure. + opts->require_audio = true; + } + return true; } diff --git a/app/src/options.c b/app/src/options.c index eec81716..12283952 100644 --- a/app/src/options.c +++ b/app/src/options.c @@ -73,6 +73,7 @@ const struct scrcpy_options scrcpy_options_default = { .cleanup = true, .start_fps_counter = false, .power_on = true, + .video = true, .audio = true, .require_audio = false, .list_encoders = false, diff --git a/app/src/options.h b/app/src/options.h index 3bb0c91e..4edf3f32 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -156,6 +156,7 @@ struct scrcpy_options { bool cleanup; bool start_fps_counter; bool power_on; + bool video; bool audio; bool require_audio; bool list_encoders; diff --git a/app/src/recorder.c b/app/src/recorder.c index 2fc95eca..5cbe6873 100644 --- a/app/src/recorder.c +++ b/app/src/recorder.c @@ -152,7 +152,7 @@ sc_recorder_close_output_file(struct sc_recorder *recorder) { static inline bool sc_recorder_has_empty_queues(struct sc_recorder *recorder) { - if (sc_vecdeque_is_empty(&recorder->video_queue)) { + if (recorder->video && sc_vecdeque_is_empty(&recorder->video_queue)) { // The video queue is empty return true; } @@ -176,7 +176,7 @@ sc_recorder_process_header(struct sc_recorder *recorder) { sc_cond_wait(&recorder->stream_cond, &recorder->mutex); } - if (sc_vecdeque_is_empty(&recorder->video_queue)) { + if (recorder->video && sc_vecdeque_is_empty(&recorder->video_queue)) { assert(recorder->stopped); // If the recorder is stopped, don't process anything if there are not // at least video packets @@ -184,7 +184,11 @@ sc_recorder_process_header(struct sc_recorder *recorder) { return false; } - AVPacket *video_pkt = sc_vecdeque_pop(&recorder->video_queue); + AVPacket *video_pkt = NULL; + if (!sc_vecdeque_is_empty(&recorder->video_queue)) { + assert(recorder->video); + video_pkt = sc_vecdeque_pop(&recorder->video_queue); + } AVPacket *audio_pkt = NULL; if (!sc_vecdeque_is_empty(&recorder->audio_queue)) { @@ -196,17 +200,19 @@ sc_recorder_process_header(struct sc_recorder *recorder) { int ret = false; - if (video_pkt->pts != AV_NOPTS_VALUE) { - LOGE("The first video packet is not a config packet"); - goto end; - } + if (video_pkt) { + if (video_pkt->pts != AV_NOPTS_VALUE) { + LOGE("The first video packet is not a config packet"); + goto end; + } - assert(recorder->video_stream_index >= 0); - AVStream *video_stream = - recorder->ctx->streams[recorder->video_stream_index]; - bool ok = sc_recorder_set_extradata(video_stream, video_pkt); - if (!ok) { - goto end; + assert(recorder->video_stream_index >= 0); + AVStream *video_stream = + recorder->ctx->streams[recorder->video_stream_index]; + bool ok = sc_recorder_set_extradata(video_stream, video_pkt); + if (!ok) { + goto end; + } } if (audio_pkt) { @@ -218,13 +224,13 @@ sc_recorder_process_header(struct sc_recorder *recorder) { assert(recorder->audio_stream_index >= 0); AVStream *audio_stream = recorder->ctx->streams[recorder->audio_stream_index]; - ok = sc_recorder_set_extradata(audio_stream, audio_pkt); + bool ok = sc_recorder_set_extradata(audio_stream, audio_pkt); if (!ok) { goto end; } } - ok = avformat_write_header(recorder->ctx, NULL) >= 0; + bool ok = avformat_write_header(recorder->ctx, NULL) >= 0; if (!ok) { LOGE("Failed to write header to %s", recorder->filename); goto end; @@ -233,7 +239,9 @@ sc_recorder_process_header(struct sc_recorder *recorder) { ret = true; end: - av_packet_free(&video_pkt); + if (video_pkt) { + av_packet_free(&video_pkt); + } if (audio_pkt) { av_packet_free(&audio_pkt); } @@ -263,7 +271,8 @@ sc_recorder_process_packets(struct sc_recorder *recorder) { sc_mutex_lock(&recorder->mutex); while (!recorder->stopped) { - if (!video_pkt && !sc_vecdeque_is_empty(&recorder->video_queue)) { + if (recorder->video && !video_pkt && + !sc_vecdeque_is_empty(&recorder->video_queue)) { // A new packet may be assigned to video_pkt and be processed break; } @@ -278,6 +287,11 @@ sc_recorder_process_packets(struct sc_recorder *recorder) { // If stopped is set, continue to process the remaining events (to // finish the recording) before actually stopping. + // If there is no video, then the video_queue will remain empty forever + // and video_pkt will always be NULL. + assert(recorder->video || (!video_pkt + && sc_vecdeque_is_empty(&recorder->video_queue))); + // If there is no audio, then the audio_queue will remain empty forever // and audio_pkt will always be NULL. assert(recorder->audio || (!audio_pkt @@ -319,6 +333,9 @@ sc_recorder_process_packets(struct sc_recorder *recorder) { if (!recorder->audio) { assert(video_pkt); pts_origin = video_pkt->pts; + } else if (!recorder->video) { + assert(audio_pkt); + pts_origin = audio_pkt->pts; } else if (video_pkt && audio_pkt) { pts_origin = MIN(video_pkt->pts, audio_pkt->pts); } else if (recorder->stopped) { @@ -639,7 +656,7 @@ sc_recorder_audio_packet_sink_disable(struct sc_packet_sink *sink) { bool sc_recorder_init(struct sc_recorder *recorder, const char *filename, - enum sc_record_format format, bool audio, + enum sc_record_format format, bool video, bool audio, const struct sc_recorder_callbacks *cbs, void *cbs_userdata) { recorder->filename = strdup(filename); if (!recorder->filename) { @@ -662,6 +679,8 @@ sc_recorder_init(struct sc_recorder *recorder, const char *filename, goto error_queue_cond_destroy; } + assert(video || audio); + recorder->video = video; recorder->audio = audio; sc_vecdeque_init(&recorder->video_queue); @@ -680,13 +699,15 @@ sc_recorder_init(struct sc_recorder *recorder, const char *filename, recorder->cbs = cbs; recorder->cbs_userdata = cbs_userdata; - static const struct sc_packet_sink_ops video_ops = { - .open = sc_recorder_video_packet_sink_open, - .close = sc_recorder_video_packet_sink_close, - .push = sc_recorder_video_packet_sink_push, - }; + if (video) { + static const struct sc_packet_sink_ops video_ops = { + .open = sc_recorder_video_packet_sink_open, + .close = sc_recorder_video_packet_sink_close, + .push = sc_recorder_video_packet_sink_push, + }; - recorder->video_packet_sink.ops = &video_ops; + recorder->video_packet_sink.ops = &video_ops; + } if (audio) { static const struct sc_packet_sink_ops audio_ops = { diff --git a/app/src/recorder.h b/app/src/recorder.h index 41b8db65..9c6cd4f9 100644 --- a/app/src/recorder.h +++ b/app/src/recorder.h @@ -27,6 +27,7 @@ struct sc_recorder { * may access it without data races. */ bool audio; + bool video; char *filename; enum sc_record_format format; @@ -59,7 +60,7 @@ struct sc_recorder_callbacks { bool sc_recorder_init(struct sc_recorder *recorder, const char *filename, - enum sc_record_format format, bool audio, + enum sc_record_format format, bool video, bool audio, const struct sc_recorder_callbacks *cbs, void *cbs_userdata); bool diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index 03b643f6..39a2f1a4 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -345,6 +345,7 @@ scrcpy(struct scrcpy_options *options) { .lock_video_orientation = options->lock_video_orientation, .control = options->control, .display_id = options->display_id, + .video = options->video, .audio = options->audio, .show_touches = options->show_touches, .stay_awake = options->stay_awake, @@ -389,7 +390,7 @@ scrcpy(struct scrcpy_options *options) { sdl_set_hints(options->render_driver); // Initialize SDL video and audio in addition if mirroring is enabled - if (SDL_Init(SDL_INIT_VIDEO)) { + if (options->video && SDL_Init(SDL_INIT_VIDEO)) { LOGE("Could not initialize SDL video: %s", SDL_GetError()); goto end; } @@ -436,11 +437,13 @@ scrcpy(struct scrcpy_options *options) { file_pusher_initialized = true; } - static const struct sc_demuxer_callbacks video_demuxer_cbs = { - .on_ended = sc_video_demuxer_on_ended, - }; - sc_demuxer_init(&s->video_demuxer, "video", s->server.video_socket, - &video_demuxer_cbs, NULL); + if (options->video) { + static const struct sc_demuxer_callbacks video_demuxer_cbs = { + .on_ended = sc_video_demuxer_on_ended, + }; + sc_demuxer_init(&s->video_demuxer, "video", s->server.video_socket, + &video_demuxer_cbs, NULL); + } if (options->audio) { static const struct sc_demuxer_callbacks audio_demuxer_cbs = { @@ -450,7 +453,7 @@ scrcpy(struct scrcpy_options *options) { &audio_demuxer_cbs, options); } - bool needs_video_decoder = options->mirror; + bool needs_video_decoder = options->mirror && options->video; bool needs_audio_decoder = options->mirror && options->audio; #ifdef HAVE_V4L2 needs_video_decoder |= !!options->v4l2_device; @@ -471,8 +474,8 @@ scrcpy(struct scrcpy_options *options) { .on_ended = sc_recorder_on_ended, }; if (!sc_recorder_init(&s->recorder, options->record_filename, - options->record_format, options->audio, - &recorder_cbs, NULL)) { + options->record_format, options->video, + options->audio, &recorder_cbs, NULL)) { goto end; } recorder_initialized = true; @@ -482,8 +485,10 @@ scrcpy(struct scrcpy_options *options) { } recorder_started = true; - sc_packet_source_add_sink(&s->video_demuxer.packet_source, - &s->recorder.video_packet_sink); + if (options->video) { + sc_packet_source_add_sink(&s->video_demuxer.packet_source, + &s->recorder.video_packet_sink); + } if (options->audio) { sc_packet_source_add_sink(&s->audio_demuxer.packet_source, &s->recorder.audio_packet_sink); @@ -671,11 +676,6 @@ aoa_hid_end: .start_fps_counter = options->start_fps_counter, }; - if (!sc_screen_init(&s->screen, &screen_params)) { - goto end; - } - screen_initialized = true; - struct sc_frame_source *src = &s->video_decoder.frame_source; if (options->display_buffer) { sc_delay_buffer_init(&s->display_buffer, options->display_buffer, @@ -684,7 +684,14 @@ aoa_hid_end: src = &s->display_buffer.frame_source; } - sc_frame_source_add_sink(src, &s->screen.frame_sink); + if (options->video) { + if (!sc_screen_init(&s->screen, &screen_params)) { + goto end; + } + screen_initialized = true; + + sc_frame_source_add_sink(src, &s->screen.frame_sink); + } if (options->audio) { sc_audio_player_init(&s->audio_player, options->audio_buffer, @@ -713,12 +720,15 @@ aoa_hid_end: } #endif - // now we consumed the header values, the socket receives the video stream - // start the video demuxer - if (!sc_demuxer_start(&s->video_demuxer)) { - goto end; + // Now that the header values have been consumed, the socket(s) will + // receive the stream(s). Start the demuxer(s). + + if (options->video) { + if (!sc_demuxer_start(&s->video_demuxer)) { + goto end; + } + video_demuxer_started = true; } - video_demuxer_started = true; if (options->audio) { if (!sc_demuxer_start(&s->audio_demuxer)) { diff --git a/app/src/server.c b/app/src/server.c index 2b35c085..9c554760 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -226,6 +226,9 @@ execute_server(struct sc_server *server, ADD_PARAM("scid=%08x", params->scid); ADD_PARAM("log_level=%s", log_level_to_server_string(params->log_level)); + if (!params->video) { + ADD_PARAM("video=false"); + } if (params->video_bit_rate) { ADD_PARAM("video_bit_rate=%" PRIu32, params->video_bit_rate); } @@ -464,6 +467,7 @@ sc_server_connect_to(struct sc_server *server, struct sc_server_info *info) { const char *serial = server->serial; assert(serial); + bool video = server->params.video; bool audio = server->params.audio; bool control = server->params.control; @@ -471,9 +475,12 @@ sc_server_connect_to(struct sc_server *server, struct sc_server_info *info) { sc_socket audio_socket = SC_SOCKET_NONE; sc_socket control_socket = SC_SOCKET_NONE; if (!tunnel->forward) { - video_socket = net_accept_intr(&server->intr, tunnel->server_socket); - if (video_socket == SC_SOCKET_NONE) { - goto fail; + if (video) { + video_socket = + net_accept_intr(&server->intr, tunnel->server_socket); + if (video_socket == SC_SOCKET_NONE) { + goto fail; + } } if (audio) { @@ -504,35 +511,45 @@ sc_server_connect_to(struct sc_server *server, struct sc_server_info *info) { unsigned attempts = 100; sc_tick delay = SC_TICK_FROM_MS(100); - video_socket = connect_to_server(server, attempts, delay, tunnel_host, - tunnel_port); - if (video_socket == SC_SOCKET_NONE) { + sc_socket first_socket = connect_to_server(server, attempts, delay, + tunnel_host, tunnel_port); + if (first_socket == SC_SOCKET_NONE) { goto fail; } + if (video) { + video_socket = first_socket; + } + if (audio) { - audio_socket = net_socket(); - if (audio_socket == SC_SOCKET_NONE) { - goto fail; - } - bool ok = net_connect_intr(&server->intr, audio_socket, tunnel_host, - tunnel_port); - if (!ok) { - goto fail; + if (!video) { + audio_socket = first_socket; + } else { + audio_socket = net_socket(); + if (audio_socket == SC_SOCKET_NONE) { + goto fail; + } + bool ok = net_connect_intr(&server->intr, audio_socket, tunnel_host, + tunnel_port); + if (!ok) { + goto fail; + } } } if (control) { - // we know that the device is listening, we don't need several - // attempts - control_socket = net_socket(); - if (control_socket == SC_SOCKET_NONE) { - goto fail; - } - bool ok = net_connect_intr(&server->intr, control_socket, - tunnel_host, tunnel_port); - if (!ok) { - goto fail; + if (!video && !audio) { + control_socket = first_socket; + } else { + control_socket = net_socket(); + if (control_socket == SC_SOCKET_NONE) { + goto fail; + } + bool ok = net_connect_intr(&server->intr, control_socket, + tunnel_host, tunnel_port); + if (!ok) { + goto fail; + } } } } @@ -541,13 +558,17 @@ sc_server_connect_to(struct sc_server *server, struct sc_server_info *info) { sc_adb_tunnel_close(tunnel, &server->intr, serial, server->device_socket_name); + sc_socket first_socket = video ? video_socket + : audio ? audio_socket + : control_socket; + // The sockets will be closed on stop if device_read_info() fails - bool ok = device_read_info(&server->intr, video_socket, info); + bool ok = device_read_info(&server->intr, first_socket, info); if (!ok) { goto fail; } - assert(video_socket != SC_SOCKET_NONE); + assert(!video || video_socket != SC_SOCKET_NONE); assert(!audio || audio_socket != SC_SOCKET_NONE); assert(!control || control_socket != SC_SOCKET_NONE); @@ -931,8 +952,11 @@ run_server(void *data) { sc_mutex_unlock(&server->mutex); // Interrupt sockets to wake up socket blocking calls on the server - assert(server->video_socket != SC_SOCKET_NONE); - net_interrupt(server->video_socket); + + if (server->video_socket != SC_SOCKET_NONE) { + // There is no video_socket if --no-video is set + net_interrupt(server->video_socket); + } if (server->audio_socket != SC_SOCKET_NONE) { // There is no audio_socket if --no-audio is set diff --git a/app/src/server.h b/app/src/server.h index c425856b..31648445 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -41,6 +41,7 @@ struct sc_server_params { int8_t lock_video_orientation; bool control; uint32_t display_id; + bool video; bool audio; bool show_touches; bool stay_awake; diff --git a/doc/audio.md b/doc/audio.md index 6e97b103..08868eaf 100644 --- a/doc/audio.md +++ b/doc/audio.md @@ -24,6 +24,21 @@ To disable audio: scrcpy --no-audio ``` +## Audio only + +To play audio only, disable the video: + +``` +scrcpy --no-video +``` + +Without video, the audio latency is typically not criticial, so it might be +interesting to add [buffering](#buffering) to minimize glitches: + +``` +scrcpy --no-video --audio-buffer=200 +``` + ## Codec The audio codec can be selected. The possible values are `opus` (default), `aac` diff --git a/doc/recording.md b/doc/recording.md index 9455a6fc..e3e2cf68 100644 --- a/doc/recording.md +++ b/doc/recording.md @@ -13,7 +13,11 @@ To record only the video: scrcpy --no-audio --record=file.mp4 ``` -_It is currently not possible to record only the audio._ +To record only the audio: + +```bash +scrcpy --no-video --record=file.mp4 +``` To disable mirroring while recording: diff --git a/doc/video.md b/doc/video.md index 58aa5022..303d9048 100644 --- a/doc/video.md +++ b/doc/video.md @@ -170,6 +170,16 @@ scrcpy --v4l2-sink=/dev/video2 --no-mirror scrcpy --record=file.mkv --no-mirror ``` + +## No video + +To disable video forwarding completely, so that only audio is forwarded: + +``` +scrcpy --no-video +``` + + ## Video4Linux See the dedicated [Video4Linux](v4l2.md) page. diff --git a/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java b/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java index 4bfff726..20ab1f9c 100644 --- a/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java +++ b/server/src/main/java/com/genymobile/scrcpy/DesktopConnection.java @@ -41,7 +41,7 @@ public final class DesktopConnection implements Closeable { controlInputStream = null; controlOutputStream = null; } - videoFd = videoSocket.getFileDescriptor(); + videoFd = videoSocket != null ? videoSocket.getFileDescriptor() : null; audioFd = audioSocket != null ? audioSocket.getFileDescriptor() : null; } @@ -60,29 +60,43 @@ public final class DesktopConnection implements Closeable { return SOCKET_NAME_PREFIX + String.format("_%08x", scid); } - public static DesktopConnection open(int scid, boolean tunnelForward, boolean audio, boolean control, boolean sendDummyByte) throws IOException { + public static DesktopConnection open(int scid, boolean tunnelForward, boolean video, boolean audio, boolean control, boolean sendDummyByte) + throws IOException { String socketName = getSocketName(scid); + LocalSocket firstSocket = null; + LocalSocket videoSocket = null; LocalSocket audioSocket = null; LocalSocket controlSocket = null; try { if (tunnelForward) { try (LocalServerSocket localServerSocket = new LocalServerSocket(socketName)) { - videoSocket = localServerSocket.accept(); - if (sendDummyByte) { - // send one byte so the client may read() to detect a connection error - videoSocket.getOutputStream().write(0); + if (video) { + videoSocket = localServerSocket.accept(); + firstSocket = videoSocket; } if (audio) { audioSocket = localServerSocket.accept(); + if (firstSocket == null) { + firstSocket = audioSocket; + } } if (control) { controlSocket = localServerSocket.accept(); + if (firstSocket == null) { + firstSocket = controlSocket; + } + } + if (sendDummyByte) { + // send one byte so the client may read() to detect a connection error + firstSocket.getOutputStream().write(0); } } } else { - videoSocket = connect(socketName); + if (video) { + videoSocket = connect(socketName); + } if (audio) { audioSocket = connect(socketName); } @@ -106,10 +120,22 @@ public final class DesktopConnection implements Closeable { return new DesktopConnection(videoSocket, audioSocket, controlSocket); } + private LocalSocket getFirstSocket() { + if (videoSocket != null) { + return videoSocket; + } + if (audioSocket != null) { + return audioSocket; + } + return controlSocket; + } + public void close() throws IOException { - videoSocket.shutdownInput(); - videoSocket.shutdownOutput(); - videoSocket.close(); + if (videoSocket != null) { + videoSocket.shutdownInput(); + videoSocket.shutdownOutput(); + videoSocket.close(); + } if (audioSocket != null) { audioSocket.shutdownInput(); audioSocket.shutdownOutput(); @@ -130,7 +156,8 @@ public final class DesktopConnection implements Closeable { System.arraycopy(deviceNameBytes, 0, buffer, 0, len); // byte[] are always 0-initialized in java, no need to set '\0' explicitly - IO.writeFully(videoFd, buffer, 0, buffer.length); + FileDescriptor fd = getFirstSocket().getFileDescriptor(); + IO.writeFully(fd, buffer, 0, buffer.length); } public FileDescriptor getVideoFd() { diff --git a/server/src/main/java/com/genymobile/scrcpy/Options.java b/server/src/main/java/com/genymobile/scrcpy/Options.java index a34eb9b5..7bd94cb3 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Options.java +++ b/server/src/main/java/com/genymobile/scrcpy/Options.java @@ -9,6 +9,7 @@ public class Options { private Ln.Level logLevel = Ln.Level.DEBUG; private int scid = -1; // 31-bit non-negative value, or -1 + private boolean video = true; private boolean audio = true; private int maxSize; private VideoCodec videoCodec = VideoCodec.H264; @@ -51,6 +52,10 @@ public class Options { return scid; } + public boolean getVideo() { + return video; + } + public boolean getAudio() { return audio; } @@ -200,6 +205,9 @@ public class Options { case "log_level": options.logLevel = Ln.Level.valueOf(value.toUpperCase(Locale.ENGLISH)); break; + case "video": + options.video = Boolean.parseBoolean(value); + break; case "audio": options.audio = Boolean.parseBoolean(value); break; diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 4d72d1e8..db993830 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -95,6 +95,7 @@ public final class Server { int scid = options.getScid(); boolean tunnelForward = options.isTunnelForward(); boolean control = options.getControl(); + boolean video = options.getVideo(); boolean audio = options.getAudio(); boolean sendDummyByte = options.getSendDummyByte(); @@ -121,7 +122,7 @@ public final class Server { List asyncProcessors = new ArrayList<>(); - DesktopConnection connection = DesktopConnection.open(scid, tunnelForward, audio, control, sendDummyByte); + DesktopConnection connection = DesktopConnection.open(scid, tunnelForward, video, audio, control, sendDummyByte); try { if (options.getSendDeviceMeta()) { connection.sendDeviceMeta(Device.getDeviceName()); @@ -147,11 +148,13 @@ public final class Server { asyncProcessors.add(audioRecorder); } - Streamer videoStreamer = new Streamer(connection.getVideoFd(), options.getVideoCodec(), options.getSendCodecMeta(), - options.getSendFrameMeta()); - ScreenEncoder screenEncoder = new ScreenEncoder(device, videoStreamer, options.getVideoBitRate(), options.getMaxFps(), - options.getVideoCodecOptions(), options.getVideoEncoder(), options.getDownsizeOnError()); - asyncProcessors.add(screenEncoder); + if (video) { + Streamer videoStreamer = new Streamer(connection.getVideoFd(), options.getVideoCodec(), options.getSendCodecMeta(), + options.getSendFrameMeta()); + ScreenEncoder screenEncoder = new ScreenEncoder(device, videoStreamer, options.getVideoBitRate(), options.getMaxFps(), + options.getVideoCodecOptions(), options.getVideoEncoder(), options.getDownsizeOnError()); + asyncProcessors.add(screenEncoder); + } Completion completion = new Completion(asyncProcessors.size()); for (AsyncProcessor asyncProcessor : asyncProcessors) {