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);