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
17 changed files with 243 additions and 96 deletions
|
@ -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
|
||||||
|
|
|
@ -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]'
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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 = {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)) {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
15
doc/audio.md
15
doc/audio.md
|
@ -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`
|
||||||
|
|
|
@ -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:
|
||||||
|
|
||||||
|
|
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
|
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.
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
Loading…
Reference in a new issue