From 13a3395a332eca55c884edc05a5052457b519f60 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Sat, 18 Feb 2023 18:09:18 +0100 Subject: [PATCH] Disable audio on initialization error By default, audio is enabled (--no-audio must be explicitly passed to disable it). However, some devices may not support audio capture (typically devices below Android 11, or Android 11 when the shell application is not foreground on start). In that case, make the server notify the client to dynamically disable audio forwarding so that it does not wait indefinitely for an audio stream. Also disable audio on unknown codec or missing decoder on the client-side, for the same reasons. PR #3757 --- app/src/demuxer.c | 28 +++++++++++++-- app/src/recorder.c | 34 ++++++++++++++++++- app/src/recorder.h | 11 ++++++ app/src/trait/packet_sink.h | 10 ++++++ .../com/genymobile/scrcpy/AudioEncoder.java | 4 +++ .../java/com/genymobile/scrcpy/Streamer.java | 6 ++++ 6 files changed, 90 insertions(+), 3 deletions(-) diff --git a/app/src/demuxer.c b/app/src/demuxer.c index 968e6db0..482f2e04 100644 --- a/app/src/demuxer.c +++ b/app/src/demuxer.c @@ -158,6 +158,16 @@ sc_demuxer_open_sinks(struct sc_demuxer *demuxer, const AVCodec *codec) { return true; } +static void +sc_demuxer_disable_sinks(struct sc_demuxer *demuxer) { + for (unsigned i = 0; i < demuxer->sink_count; ++i) { + struct sc_packet_sink *sink = demuxer->sinks[i]; + if (sink->ops->disable) { + sink->ops->disable(sink); + } + } +} + static int run_demuxer(void *data) { struct sc_demuxer *demuxer = data; @@ -168,19 +178,33 @@ run_demuxer(void *data) { uint32_t raw_codec_id; bool ok = sc_demuxer_recv_codec_id(demuxer, &raw_codec_id); if (!ok) { + LOGE("Demuxer '%s': stream disabled due to connection error", + demuxer->name); + eos = true; + goto end; + } + + if (raw_codec_id == 0) { + LOGW("Demuxer '%s': stream explicitly disabled by the device", + demuxer->name); + sc_demuxer_disable_sinks(demuxer); eos = true; goto end; } enum AVCodecID codec_id = sc_demuxer_to_avcodec_id(raw_codec_id); if (codec_id == AV_CODEC_ID_NONE) { - // Error already logged + LOGE("Demuxer '%s': stream disabled due to unsupported codec", + demuxer->name); + sc_demuxer_disable_sinks(demuxer); goto end; } const AVCodec *codec = avcodec_find_decoder(codec_id); if (!codec) { - LOGE("Demuxer '%s': decoder not found", demuxer->name); + LOGE("Demuxer '%s': stream disabled due to missing decoder", + demuxer->name); + sc_demuxer_disable_sinks(demuxer); goto end; } diff --git a/app/src/recorder.c b/app/src/recorder.c index 93c3321f..c694f022 100644 --- a/app/src/recorder.c +++ b/app/src/recorder.c @@ -194,9 +194,17 @@ sc_recorder_wait_video_stream(struct sc_recorder *recorder) { static bool sc_recorder_wait_audio_stream(struct sc_recorder *recorder) { sc_mutex_lock(&recorder->mutex); - while (!recorder->audio_codec && !recorder->stopped) { + while (!recorder->audio_codec && !recorder->audio_disabled + && !recorder->stopped) { sc_cond_wait(&recorder->stream_cond, &recorder->mutex); } + + if (recorder->audio_disabled) { + // Reset audio flag. From there, the recorder thread may access this + // flag without any mutex. + recorder->audio = false; + } + const AVCodec *codec = recorder->audio_codec; sc_mutex_unlock(&recorder->mutex); @@ -585,6 +593,8 @@ sc_recorder_audio_packet_sink_open(struct sc_packet_sink *sink, const AVCodec *codec) { struct sc_recorder *recorder = DOWNCAST_AUDIO(sink); assert(recorder->audio); + // only written from this thread, no need to lock + assert(!recorder->audio_disabled); assert(codec); sc_mutex_lock(&recorder->mutex); @@ -599,6 +609,8 @@ static void sc_recorder_audio_packet_sink_close(struct sc_packet_sink *sink) { struct sc_recorder *recorder = DOWNCAST_AUDIO(sink); assert(recorder->audio); + // only written from this thread, no need to lock + assert(!recorder->audio_disabled); sc_mutex_lock(&recorder->mutex); // EOS also stops the recorder @@ -612,6 +624,8 @@ sc_recorder_audio_packet_sink_push(struct sc_packet_sink *sink, const AVPacket *packet) { struct sc_recorder *recorder = DOWNCAST_AUDIO(sink); assert(recorder->audio); + // only written from this thread, no need to lock + assert(!recorder->audio_disabled); sc_mutex_lock(&recorder->mutex); @@ -637,6 +651,22 @@ sc_recorder_audio_packet_sink_push(struct sc_packet_sink *sink, return true; } +static void +sc_recorder_audio_packet_sink_disable(struct sc_packet_sink *sink) { + struct sc_recorder *recorder = DOWNCAST_AUDIO(sink); + assert(recorder->audio); + // only written from this thread, no need to lock + assert(!recorder->audio_disabled); + assert(!recorder->audio_codec); + + LOGW("Audio stream recording disabled"); + + sc_mutex_lock(&recorder->mutex); + recorder->audio_disabled = true; + sc_cond_signal(&recorder->stream_cond); + sc_mutex_unlock(&recorder->mutex); +} + bool sc_recorder_init(struct sc_recorder *recorder, const char *filename, enum sc_record_format format, bool audio, @@ -671,6 +701,7 @@ sc_recorder_init(struct sc_recorder *recorder, const char *filename, recorder->video_codec = NULL; recorder->audio_codec = NULL; + recorder->audio_disabled = false; recorder->video_stream_index = -1; recorder->audio_stream_index = -1; @@ -695,6 +726,7 @@ sc_recorder_init(struct sc_recorder *recorder, const char *filename, .open = sc_recorder_audio_packet_sink_open, .close = sc_recorder_audio_packet_sink_close, .push = sc_recorder_audio_packet_sink_push, + .disable = sc_recorder_audio_packet_sink_disable, }; recorder->audio_packet_sink.ops = &audio_ops; diff --git a/app/src/recorder.h b/app/src/recorder.h index ed880de0..6fe72401 100644 --- a/app/src/recorder.h +++ b/app/src/recorder.h @@ -23,6 +23,14 @@ struct sc_recorder { struct sc_packet_sink video_packet_sink; struct sc_packet_sink audio_packet_sink; + /* The audio flag is unprotected: + * - it is initialized from sc_recorder_init() from the main thread; + * - it may be reset once from the recorder thread if the audio is + * disabled dynamically. + * + * Therefore, once the recorder thread is started, only the recorder thread + * may access it without data races. + */ bool audio; char *filename; @@ -42,6 +50,9 @@ struct sc_recorder { sc_cond stream_cond; const AVCodec *video_codec; const AVCodec *audio_codec; + // Instead of providing an audio_codec, the demuxer may notify that the + // stream is disabled if the device could not capture audio + bool audio_disabled; int video_stream_index; int audio_stream_index; diff --git a/app/src/trait/packet_sink.h b/app/src/trait/packet_sink.h index 9fc9fd24..099c8c52 100644 --- a/app/src/trait/packet_sink.h +++ b/app/src/trait/packet_sink.h @@ -23,6 +23,16 @@ struct sc_packet_sink_ops { bool (*open)(struct sc_packet_sink *sink, const AVCodec *codec); void (*close)(struct sc_packet_sink *sink); bool (*push)(struct sc_packet_sink *sink, const AVPacket *packet); + + /*/ + * Called when the input stream has been disabled at runtime. + * + * If it is called, then open(), close() and push() will never be called. + * + * It is useful to notify the recorder that the requested audio stream has + * finally been disabled because the device could not capture it. + */ + void (*disable)(struct sc_packet_sink *sink); }; #endif diff --git a/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java b/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java index 74481f99..8bc25e8e 100644 --- a/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java +++ b/server/src/main/java/com/genymobile/scrcpy/AudioEncoder.java @@ -255,6 +255,10 @@ public final class AudioEncoder { outputThread.start(); waitEnded(); + } catch (Throwable e) { + // Notify the client that the audio could not be captured + streamer.writeDisableStream(); + throw e; } finally { // Cleanup everything (either at the end or on error at any step of the initialization) if (mediaCodecThread != null) { diff --git a/server/src/main/java/com/genymobile/scrcpy/Streamer.java b/server/src/main/java/com/genymobile/scrcpy/Streamer.java index 77d9eefa..7cc065eb 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Streamer.java +++ b/server/src/main/java/com/genymobile/scrcpy/Streamer.java @@ -40,6 +40,12 @@ public final class Streamer { } } + public void writeDisableStream() throws IOException { + // Writing 0 (32-bit) as codec-id means that the device disables the stream (because it could not capture) + byte[] zeros = new byte[4]; + IO.writeFully(fd, zeros, 0, zeros.length); + } + public void writePacket(ByteBuffer buffer, long pts, boolean config, boolean keyFrame) throws IOException { if (config && codec == AudioCodec.OPUS) { fixOpusConfigPacket(buffer);