Add --no-video

Similar to --no-audio, add --no-video to play audio only.

Fixes #3842 <https://github.com/Genymobile/scrcpy/issues/3842>
PR #3978 <https://github.com/Genymobile/scrcpy/pull/3978>
This commit is contained in:
Romain Vimont 2023-05-07 12:08:50 +02:00
parent e89e772c7c
commit 8c650e53cd
17 changed files with 243 additions and 96 deletions

View file

@ -37,6 +37,7 @@ _scrcpy() {
--no-key-repeat --no-key-repeat
--no-mipmaps --no-mipmaps
--no-power-on --no-power-on
--no-video
--otg --otg
-p --port= -p --port=
--power-off-on-close --power-off-on-close

View file

@ -43,6 +43,7 @@ arguments=(
'--no-key-repeat[Do not forward repeated key events when a key is held down]' '--no-key-repeat[Do not forward repeated key events when a key is held down]'
'--no-mipmaps[Disable the generation of mipmaps]' '--no-mipmaps[Disable the generation of mipmaps]'
'--no-power-on[Do not power on the device on start]' '--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\)]' '--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]' {-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]' '--power-off-on-close[Turn the device screen off when closing scrcpy]'

View file

@ -225,6 +225,10 @@ If the renderer is OpenGL 3.0+ or OpenGL ES 2.0+, then mipmaps are automatically
.B \-\-no\-power\-on .B \-\-no\-power\-on
Do not power on the device on start. Do not power on the device on start.
.TP
.B \-\-no\-video
Disable video forwarding.
.TP .TP
.B \-\-otg .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. 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.

View file

@ -73,6 +73,7 @@ enum {
OPT_AUDIO_BUFFER, OPT_AUDIO_BUFFER,
OPT_AUDIO_OUTPUT_BUFFER, OPT_AUDIO_OUTPUT_BUFFER,
OPT_NO_DISPLAY, OPT_NO_DISPLAY,
OPT_NO_VIDEO,
}; };
struct sc_option { struct sc_option {
@ -407,6 +408,11 @@ static const struct sc_option options[] = {
.longopt = "no-power-on", .longopt = "no-power-on",
.text = "Do not power on the device on start.", .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_id = OPT_OTG,
.longopt = "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: case OPT_NO_DOWNSIZE_ON_ERROR:
opts->downsize_on_error = false; opts->downsize_on_error = false;
break; break;
case OPT_NO_VIDEO:
opts->video = false;
break;
case OPT_NO_AUDIO: case OPT_NO_AUDIO:
opts->audio = false; opts->audio = false;
break; break;
@ -2042,14 +2051,20 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
#endif #endif
#ifdef HAVE_USB #ifdef HAVE_USB
if (!opts->mirror && opts->control && !opts->otg) { if (!(opts->mirror && opts->video) && !opts->otg) {
#else #else
if (!opts->mirror && opts->control) { if (!(opts->mirror && opts->video)) {
#endif #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; opts->control = false;
} }
if (!opts->video) {
// If video is disabled, then scrcpy must exit on audio failure.
opts->require_audio = true;
}
return true; return true;
} }

View file

@ -73,6 +73,7 @@ const struct scrcpy_options scrcpy_options_default = {
.cleanup = true, .cleanup = true,
.start_fps_counter = false, .start_fps_counter = false,
.power_on = true, .power_on = true,
.video = true,
.audio = true, .audio = true,
.require_audio = false, .require_audio = false,
.list_encoders = false, .list_encoders = false,

View file

@ -156,6 +156,7 @@ struct scrcpy_options {
bool cleanup; bool cleanup;
bool start_fps_counter; bool start_fps_counter;
bool power_on; bool power_on;
bool video;
bool audio; bool audio;
bool require_audio; bool require_audio;
bool list_encoders; bool list_encoders;

View file

@ -152,7 +152,7 @@ sc_recorder_close_output_file(struct sc_recorder *recorder) {
static inline bool static inline bool
sc_recorder_has_empty_queues(struct sc_recorder *recorder) { 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 // The video queue is empty
return true; return true;
} }
@ -176,7 +176,7 @@ sc_recorder_process_header(struct sc_recorder *recorder) {
sc_cond_wait(&recorder->stream_cond, &recorder->mutex); 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); assert(recorder->stopped);
// If the recorder is stopped, don't process anything if there are not // If the recorder is stopped, don't process anything if there are not
// at least video packets // at least video packets
@ -184,7 +184,11 @@ sc_recorder_process_header(struct sc_recorder *recorder) {
return false; 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; AVPacket *audio_pkt = NULL;
if (!sc_vecdeque_is_empty(&recorder->audio_queue)) { if (!sc_vecdeque_is_empty(&recorder->audio_queue)) {
@ -196,17 +200,19 @@ sc_recorder_process_header(struct sc_recorder *recorder) {
int ret = false; int ret = false;
if (video_pkt->pts != AV_NOPTS_VALUE) { if (video_pkt) {
LOGE("The first video packet is not a config packet"); if (video_pkt->pts != AV_NOPTS_VALUE) {
goto end; LOGE("The first video packet is not a config packet");
} goto end;
}
assert(recorder->video_stream_index >= 0); assert(recorder->video_stream_index >= 0);
AVStream *video_stream = AVStream *video_stream =
recorder->ctx->streams[recorder->video_stream_index]; recorder->ctx->streams[recorder->video_stream_index];
bool ok = sc_recorder_set_extradata(video_stream, video_pkt); bool ok = sc_recorder_set_extradata(video_stream, video_pkt);
if (!ok) { if (!ok) {
goto end; goto end;
}
} }
if (audio_pkt) { if (audio_pkt) {
@ -218,13 +224,13 @@ sc_recorder_process_header(struct sc_recorder *recorder) {
assert(recorder->audio_stream_index >= 0); assert(recorder->audio_stream_index >= 0);
AVStream *audio_stream = AVStream *audio_stream =
recorder->ctx->streams[recorder->audio_stream_index]; 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) { if (!ok) {
goto end; goto end;
} }
} }
ok = avformat_write_header(recorder->ctx, NULL) >= 0; bool ok = avformat_write_header(recorder->ctx, NULL) >= 0;
if (!ok) { if (!ok) {
LOGE("Failed to write header to %s", recorder->filename); LOGE("Failed to write header to %s", recorder->filename);
goto end; goto end;
@ -233,7 +239,9 @@ sc_recorder_process_header(struct sc_recorder *recorder) {
ret = true; ret = true;
end: end:
av_packet_free(&video_pkt); if (video_pkt) {
av_packet_free(&video_pkt);
}
if (audio_pkt) { if (audio_pkt) {
av_packet_free(&audio_pkt); av_packet_free(&audio_pkt);
} }
@ -263,7 +271,8 @@ sc_recorder_process_packets(struct sc_recorder *recorder) {
sc_mutex_lock(&recorder->mutex); sc_mutex_lock(&recorder->mutex);
while (!recorder->stopped) { 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 // A new packet may be assigned to video_pkt and be processed
break; break;
} }
@ -278,6 +287,11 @@ sc_recorder_process_packets(struct sc_recorder *recorder) {
// If stopped is set, continue to process the remaining events (to // If stopped is set, continue to process the remaining events (to
// finish the recording) before actually stopping. // 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 // If there is no audio, then the audio_queue will remain empty forever
// and audio_pkt will always be NULL. // and audio_pkt will always be NULL.
assert(recorder->audio || (!audio_pkt assert(recorder->audio || (!audio_pkt
@ -319,6 +333,9 @@ sc_recorder_process_packets(struct sc_recorder *recorder) {
if (!recorder->audio) { if (!recorder->audio) {
assert(video_pkt); assert(video_pkt);
pts_origin = video_pkt->pts; pts_origin = video_pkt->pts;
} else if (!recorder->video) {
assert(audio_pkt);
pts_origin = audio_pkt->pts;
} else if (video_pkt && audio_pkt) { } else if (video_pkt && audio_pkt) {
pts_origin = MIN(video_pkt->pts, audio_pkt->pts); pts_origin = MIN(video_pkt->pts, audio_pkt->pts);
} else if (recorder->stopped) { } else if (recorder->stopped) {
@ -639,7 +656,7 @@ sc_recorder_audio_packet_sink_disable(struct sc_packet_sink *sink) {
bool bool
sc_recorder_init(struct sc_recorder *recorder, const char *filename, 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) { const struct sc_recorder_callbacks *cbs, void *cbs_userdata) {
recorder->filename = strdup(filename); recorder->filename = strdup(filename);
if (!recorder->filename) { if (!recorder->filename) {
@ -662,6 +679,8 @@ sc_recorder_init(struct sc_recorder *recorder, const char *filename,
goto error_queue_cond_destroy; goto error_queue_cond_destroy;
} }
assert(video || audio);
recorder->video = video;
recorder->audio = audio; recorder->audio = audio;
sc_vecdeque_init(&recorder->video_queue); 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 = cbs;
recorder->cbs_userdata = cbs_userdata; recorder->cbs_userdata = cbs_userdata;
static const struct sc_packet_sink_ops video_ops = { if (video) {
.open = sc_recorder_video_packet_sink_open, static const struct sc_packet_sink_ops video_ops = {
.close = sc_recorder_video_packet_sink_close, .open = sc_recorder_video_packet_sink_open,
.push = sc_recorder_video_packet_sink_push, .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) { if (audio) {
static const struct sc_packet_sink_ops audio_ops = { static const struct sc_packet_sink_ops audio_ops = {

View file

@ -27,6 +27,7 @@ struct sc_recorder {
* may access it without data races. * may access it without data races.
*/ */
bool audio; bool audio;
bool video;
char *filename; char *filename;
enum sc_record_format format; enum sc_record_format format;
@ -59,7 +60,7 @@ struct sc_recorder_callbacks {
bool bool
sc_recorder_init(struct sc_recorder *recorder, const char *filename, 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); const struct sc_recorder_callbacks *cbs, void *cbs_userdata);
bool bool

View file

@ -345,6 +345,7 @@ scrcpy(struct scrcpy_options *options) {
.lock_video_orientation = options->lock_video_orientation, .lock_video_orientation = options->lock_video_orientation,
.control = options->control, .control = options->control,
.display_id = options->display_id, .display_id = options->display_id,
.video = options->video,
.audio = options->audio, .audio = options->audio,
.show_touches = options->show_touches, .show_touches = options->show_touches,
.stay_awake = options->stay_awake, .stay_awake = options->stay_awake,
@ -389,7 +390,7 @@ scrcpy(struct scrcpy_options *options) {
sdl_set_hints(options->render_driver); sdl_set_hints(options->render_driver);
// Initialize SDL video and audio in addition if mirroring is enabled // 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()); LOGE("Could not initialize SDL video: %s", SDL_GetError());
goto end; goto end;
} }
@ -436,11 +437,13 @@ scrcpy(struct scrcpy_options *options) {
file_pusher_initialized = true; file_pusher_initialized = true;
} }
static const struct sc_demuxer_callbacks video_demuxer_cbs = { if (options->video) {
.on_ended = sc_video_demuxer_on_ended, 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); sc_demuxer_init(&s->video_demuxer, "video", s->server.video_socket,
&video_demuxer_cbs, NULL);
}
if (options->audio) { if (options->audio) {
static const struct sc_demuxer_callbacks audio_demuxer_cbs = { static const struct sc_demuxer_callbacks audio_demuxer_cbs = {
@ -450,7 +453,7 @@ scrcpy(struct scrcpy_options *options) {
&audio_demuxer_cbs, 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; bool needs_audio_decoder = options->mirror && options->audio;
#ifdef HAVE_V4L2 #ifdef HAVE_V4L2
needs_video_decoder |= !!options->v4l2_device; needs_video_decoder |= !!options->v4l2_device;
@ -471,8 +474,8 @@ scrcpy(struct scrcpy_options *options) {
.on_ended = sc_recorder_on_ended, .on_ended = sc_recorder_on_ended,
}; };
if (!sc_recorder_init(&s->recorder, options->record_filename, if (!sc_recorder_init(&s->recorder, options->record_filename,
options->record_format, options->audio, options->record_format, options->video,
&recorder_cbs, NULL)) { options->audio, &recorder_cbs, NULL)) {
goto end; goto end;
} }
recorder_initialized = true; recorder_initialized = true;
@ -482,8 +485,10 @@ scrcpy(struct scrcpy_options *options) {
} }
recorder_started = true; recorder_started = true;
sc_packet_source_add_sink(&s->video_demuxer.packet_source, if (options->video) {
&s->recorder.video_packet_sink); sc_packet_source_add_sink(&s->video_demuxer.packet_source,
&s->recorder.video_packet_sink);
}
if (options->audio) { if (options->audio) {
sc_packet_source_add_sink(&s->audio_demuxer.packet_source, sc_packet_source_add_sink(&s->audio_demuxer.packet_source,
&s->recorder.audio_packet_sink); &s->recorder.audio_packet_sink);
@ -671,11 +676,6 @@ aoa_hid_end:
.start_fps_counter = options->start_fps_counter, .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; struct sc_frame_source *src = &s->video_decoder.frame_source;
if (options->display_buffer) { if (options->display_buffer) {
sc_delay_buffer_init(&s->display_buffer, 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; 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) { if (options->audio) {
sc_audio_player_init(&s->audio_player, options->audio_buffer, sc_audio_player_init(&s->audio_player, options->audio_buffer,
@ -713,12 +720,15 @@ aoa_hid_end:
} }
#endif #endif
// now we consumed the header values, the socket receives the video stream // Now that the header values have been consumed, the socket(s) will
// start the video demuxer // receive the stream(s). Start the demuxer(s).
if (!sc_demuxer_start(&s->video_demuxer)) {
goto end; if (options->video) {
if (!sc_demuxer_start(&s->video_demuxer)) {
goto end;
}
video_demuxer_started = true;
} }
video_demuxer_started = true;
if (options->audio) { if (options->audio) {
if (!sc_demuxer_start(&s->audio_demuxer)) { if (!sc_demuxer_start(&s->audio_demuxer)) {

View file

@ -226,6 +226,9 @@ execute_server(struct sc_server *server,
ADD_PARAM("scid=%08x", params->scid); ADD_PARAM("scid=%08x", params->scid);
ADD_PARAM("log_level=%s", log_level_to_server_string(params->log_level)); 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) { if (params->video_bit_rate) {
ADD_PARAM("video_bit_rate=%" PRIu32, 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; const char *serial = server->serial;
assert(serial); assert(serial);
bool video = server->params.video;
bool audio = server->params.audio; bool audio = server->params.audio;
bool control = server->params.control; 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 audio_socket = SC_SOCKET_NONE;
sc_socket control_socket = SC_SOCKET_NONE; sc_socket control_socket = SC_SOCKET_NONE;
if (!tunnel->forward) { if (!tunnel->forward) {
video_socket = net_accept_intr(&server->intr, tunnel->server_socket); if (video) {
if (video_socket == SC_SOCKET_NONE) { video_socket =
goto fail; net_accept_intr(&server->intr, tunnel->server_socket);
if (video_socket == SC_SOCKET_NONE) {
goto fail;
}
} }
if (audio) { if (audio) {
@ -504,35 +511,45 @@ sc_server_connect_to(struct sc_server *server, struct sc_server_info *info) {
unsigned attempts = 100; unsigned attempts = 100;
sc_tick delay = SC_TICK_FROM_MS(100); sc_tick delay = SC_TICK_FROM_MS(100);
video_socket = connect_to_server(server, attempts, delay, tunnel_host, sc_socket first_socket = connect_to_server(server, attempts, delay,
tunnel_port); tunnel_host, tunnel_port);
if (video_socket == SC_SOCKET_NONE) { if (first_socket == SC_SOCKET_NONE) {
goto fail; goto fail;
} }
if (video) {
video_socket = first_socket;
}
if (audio) { if (audio) {
audio_socket = net_socket(); if (!video) {
if (audio_socket == SC_SOCKET_NONE) { audio_socket = first_socket;
goto fail; } else {
} audio_socket = net_socket();
bool ok = net_connect_intr(&server->intr, audio_socket, tunnel_host, if (audio_socket == SC_SOCKET_NONE) {
tunnel_port); goto fail;
if (!ok) { }
goto fail; bool ok = net_connect_intr(&server->intr, audio_socket, tunnel_host,
tunnel_port);
if (!ok) {
goto fail;
}
} }
} }
if (control) { if (control) {
// we know that the device is listening, we don't need several if (!video && !audio) {
// attempts control_socket = first_socket;
control_socket = net_socket(); } else {
if (control_socket == SC_SOCKET_NONE) { control_socket = net_socket();
goto fail; if (control_socket == SC_SOCKET_NONE) {
} goto fail;
bool ok = net_connect_intr(&server->intr, control_socket, }
tunnel_host, tunnel_port); bool ok = net_connect_intr(&server->intr, control_socket,
if (!ok) { tunnel_host, tunnel_port);
goto fail; 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, sc_adb_tunnel_close(tunnel, &server->intr, serial,
server->device_socket_name); 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 // 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) { if (!ok) {
goto fail; goto fail;
} }
assert(video_socket != SC_SOCKET_NONE); assert(!video || video_socket != SC_SOCKET_NONE);
assert(!audio || audio_socket != SC_SOCKET_NONE); assert(!audio || audio_socket != SC_SOCKET_NONE);
assert(!control || control_socket != SC_SOCKET_NONE); assert(!control || control_socket != SC_SOCKET_NONE);
@ -931,8 +952,11 @@ run_server(void *data) {
sc_mutex_unlock(&server->mutex); sc_mutex_unlock(&server->mutex);
// Interrupt sockets to wake up socket blocking calls on the server // 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) { if (server->audio_socket != SC_SOCKET_NONE) {
// There is no audio_socket if --no-audio is set // There is no audio_socket if --no-audio is set

View file

@ -41,6 +41,7 @@ struct sc_server_params {
int8_t lock_video_orientation; int8_t lock_video_orientation;
bool control; bool control;
uint32_t display_id; uint32_t display_id;
bool video;
bool audio; bool audio;
bool show_touches; bool show_touches;
bool stay_awake; bool stay_awake;

View file

@ -24,6 +24,21 @@ To disable audio:
scrcpy --no-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 ## Codec
The audio codec can be selected. The possible values are `opus` (default), `aac` The audio codec can be selected. The possible values are `opus` (default), `aac`

View file

@ -13,7 +13,11 @@ To record only the video:
scrcpy --no-audio --record=file.mp4 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: To disable mirroring while recording:

View file

@ -170,6 +170,16 @@ scrcpy --v4l2-sink=/dev/video2 --no-mirror
scrcpy --record=file.mkv --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 ## Video4Linux
See the dedicated [Video4Linux](v4l2.md) page. See the dedicated [Video4Linux](v4l2.md) page.

View file

@ -41,7 +41,7 @@ public final class DesktopConnection implements Closeable {
controlInputStream = null; controlInputStream = null;
controlOutputStream = null; controlOutputStream = null;
} }
videoFd = videoSocket.getFileDescriptor(); videoFd = videoSocket != null ? videoSocket.getFileDescriptor() : null;
audioFd = audioSocket != null ? audioSocket.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); 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); String socketName = getSocketName(scid);
LocalSocket firstSocket = null;
LocalSocket videoSocket = null; LocalSocket videoSocket = null;
LocalSocket audioSocket = null; LocalSocket audioSocket = null;
LocalSocket controlSocket = null; LocalSocket controlSocket = null;
try { try {
if (tunnelForward) { if (tunnelForward) {
try (LocalServerSocket localServerSocket = new LocalServerSocket(socketName)) { try (LocalServerSocket localServerSocket = new LocalServerSocket(socketName)) {
videoSocket = localServerSocket.accept(); if (video) {
if (sendDummyByte) { videoSocket = localServerSocket.accept();
// send one byte so the client may read() to detect a connection error firstSocket = videoSocket;
videoSocket.getOutputStream().write(0);
} }
if (audio) { if (audio) {
audioSocket = localServerSocket.accept(); audioSocket = localServerSocket.accept();
if (firstSocket == null) {
firstSocket = audioSocket;
}
} }
if (control) { if (control) {
controlSocket = localServerSocket.accept(); 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 { } else {
videoSocket = connect(socketName); if (video) {
videoSocket = connect(socketName);
}
if (audio) { if (audio) {
audioSocket = connect(socketName); audioSocket = connect(socketName);
} }
@ -106,10 +120,22 @@ public final class DesktopConnection implements Closeable {
return new DesktopConnection(videoSocket, audioSocket, controlSocket); 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 { public void close() throws IOException {
videoSocket.shutdownInput(); if (videoSocket != null) {
videoSocket.shutdownOutput(); videoSocket.shutdownInput();
videoSocket.close(); videoSocket.shutdownOutput();
videoSocket.close();
}
if (audioSocket != null) { if (audioSocket != null) {
audioSocket.shutdownInput(); audioSocket.shutdownInput();
audioSocket.shutdownOutput(); audioSocket.shutdownOutput();
@ -130,7 +156,8 @@ public final class DesktopConnection implements Closeable {
System.arraycopy(deviceNameBytes, 0, buffer, 0, len); System.arraycopy(deviceNameBytes, 0, buffer, 0, len);
// byte[] are always 0-initialized in java, no need to set '\0' explicitly // 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() { public FileDescriptor getVideoFd() {

View file

@ -9,6 +9,7 @@ public class Options {
private Ln.Level logLevel = Ln.Level.DEBUG; private Ln.Level logLevel = Ln.Level.DEBUG;
private int scid = -1; // 31-bit non-negative value, or -1 private int scid = -1; // 31-bit non-negative value, or -1
private boolean video = true;
private boolean audio = true; private boolean audio = true;
private int maxSize; private int maxSize;
private VideoCodec videoCodec = VideoCodec.H264; private VideoCodec videoCodec = VideoCodec.H264;
@ -51,6 +52,10 @@ public class Options {
return scid; return scid;
} }
public boolean getVideo() {
return video;
}
public boolean getAudio() { public boolean getAudio() {
return audio; return audio;
} }
@ -200,6 +205,9 @@ public class Options {
case "log_level": case "log_level":
options.logLevel = Ln.Level.valueOf(value.toUpperCase(Locale.ENGLISH)); options.logLevel = Ln.Level.valueOf(value.toUpperCase(Locale.ENGLISH));
break; break;
case "video":
options.video = Boolean.parseBoolean(value);
break;
case "audio": case "audio":
options.audio = Boolean.parseBoolean(value); options.audio = Boolean.parseBoolean(value);
break; break;

View file

@ -95,6 +95,7 @@ public final class Server {
int scid = options.getScid(); int scid = options.getScid();
boolean tunnelForward = options.isTunnelForward(); boolean tunnelForward = options.isTunnelForward();
boolean control = options.getControl(); boolean control = options.getControl();
boolean video = options.getVideo();
boolean audio = options.getAudio(); boolean audio = options.getAudio();
boolean sendDummyByte = options.getSendDummyByte(); boolean sendDummyByte = options.getSendDummyByte();
@ -121,7 +122,7 @@ public final class Server {
List<AsyncProcessor> asyncProcessors = new ArrayList<>(); List<AsyncProcessor> asyncProcessors = new ArrayList<>();
DesktopConnection connection = DesktopConnection.open(scid, tunnelForward, audio, control, sendDummyByte); DesktopConnection connection = DesktopConnection.open(scid, tunnelForward, video, audio, control, sendDummyByte);
try { try {
if (options.getSendDeviceMeta()) { if (options.getSendDeviceMeta()) {
connection.sendDeviceMeta(Device.getDeviceName()); connection.sendDeviceMeta(Device.getDeviceName());
@ -147,11 +148,13 @@ public final class Server {
asyncProcessors.add(audioRecorder); asyncProcessors.add(audioRecorder);
} }
Streamer videoStreamer = new Streamer(connection.getVideoFd(), options.getVideoCodec(), options.getSendCodecMeta(), if (video) {
options.getSendFrameMeta()); Streamer videoStreamer = new Streamer(connection.getVideoFd(), options.getVideoCodec(), options.getSendCodecMeta(),
ScreenEncoder screenEncoder = new ScreenEncoder(device, videoStreamer, options.getVideoBitRate(), options.getMaxFps(), options.getSendFrameMeta());
options.getVideoCodecOptions(), options.getVideoEncoder(), options.getDownsizeOnError()); ScreenEncoder screenEncoder = new ScreenEncoder(device, videoStreamer, options.getVideoBitRate(), options.getMaxFps(),
asyncProcessors.add(screenEncoder); options.getVideoCodecOptions(), options.getVideoEncoder(), options.getDownsizeOnError());
asyncProcessors.add(screenEncoder);
}
Completion completion = new Completion(asyncProcessors.size()); Completion completion = new Completion(asyncProcessors.size());
for (AsyncProcessor asyncProcessor : asyncProcessors) { for (AsyncProcessor asyncProcessor : asyncProcessors) {