mirror of https://github.com/Genymobile/scrcpy
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:
parent
e89e772c7c
commit
8c650e53cd
|
@ -37,6 +37,7 @@ _scrcpy() {
|
|||
--no-key-repeat
|
||||
--no-mipmaps
|
||||
--no-power-on
|
||||
--no-video
|
||||
--otg
|
||||
-p --port=
|
||||
--power-off-on-close
|
||||
|
|
|
@ -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]'
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
15
doc/audio.md
15
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`
|
||||
|
|
|
@ -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:
|
||||
|
||||
|
|
10
doc/video.md
10
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.
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<AsyncProcessor> 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) {
|
||||
|
|
Loading…
Reference in New Issue