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 <https://github.com/Genymobile/scrcpy/pull/3757>
This commit is contained in:
Romain Vimont 2023-02-18 18:09:18 +01:00
parent 7de0622214
commit 13a3395a33
6 changed files with 90 additions and 3 deletions

View file

@ -158,6 +158,16 @@ sc_demuxer_open_sinks(struct sc_demuxer *demuxer, const AVCodec *codec) {
return true; 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 static int
run_demuxer(void *data) { run_demuxer(void *data) {
struct sc_demuxer *demuxer = data; struct sc_demuxer *demuxer = data;
@ -168,19 +178,33 @@ run_demuxer(void *data) {
uint32_t raw_codec_id; uint32_t raw_codec_id;
bool ok = sc_demuxer_recv_codec_id(demuxer, &raw_codec_id); bool ok = sc_demuxer_recv_codec_id(demuxer, &raw_codec_id);
if (!ok) { 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; eos = true;
goto end; goto end;
} }
enum AVCodecID codec_id = sc_demuxer_to_avcodec_id(raw_codec_id); enum AVCodecID codec_id = sc_demuxer_to_avcodec_id(raw_codec_id);
if (codec_id == AV_CODEC_ID_NONE) { 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; goto end;
} }
const AVCodec *codec = avcodec_find_decoder(codec_id); const AVCodec *codec = avcodec_find_decoder(codec_id);
if (!codec) { 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; goto end;
} }

View file

@ -194,9 +194,17 @@ sc_recorder_wait_video_stream(struct sc_recorder *recorder) {
static bool static bool
sc_recorder_wait_audio_stream(struct sc_recorder *recorder) { sc_recorder_wait_audio_stream(struct sc_recorder *recorder) {
sc_mutex_lock(&recorder->mutex); 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); 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; const AVCodec *codec = recorder->audio_codec;
sc_mutex_unlock(&recorder->mutex); sc_mutex_unlock(&recorder->mutex);
@ -585,6 +593,8 @@ sc_recorder_audio_packet_sink_open(struct sc_packet_sink *sink,
const AVCodec *codec) { const AVCodec *codec) {
struct sc_recorder *recorder = DOWNCAST_AUDIO(sink); struct sc_recorder *recorder = DOWNCAST_AUDIO(sink);
assert(recorder->audio); assert(recorder->audio);
// only written from this thread, no need to lock
assert(!recorder->audio_disabled);
assert(codec); assert(codec);
sc_mutex_lock(&recorder->mutex); sc_mutex_lock(&recorder->mutex);
@ -599,6 +609,8 @@ static void
sc_recorder_audio_packet_sink_close(struct sc_packet_sink *sink) { sc_recorder_audio_packet_sink_close(struct sc_packet_sink *sink) {
struct sc_recorder *recorder = DOWNCAST_AUDIO(sink); struct sc_recorder *recorder = DOWNCAST_AUDIO(sink);
assert(recorder->audio); assert(recorder->audio);
// only written from this thread, no need to lock
assert(!recorder->audio_disabled);
sc_mutex_lock(&recorder->mutex); sc_mutex_lock(&recorder->mutex);
// EOS also stops the recorder // EOS also stops the recorder
@ -612,6 +624,8 @@ sc_recorder_audio_packet_sink_push(struct sc_packet_sink *sink,
const AVPacket *packet) { const AVPacket *packet) {
struct sc_recorder *recorder = DOWNCAST_AUDIO(sink); struct sc_recorder *recorder = DOWNCAST_AUDIO(sink);
assert(recorder->audio); assert(recorder->audio);
// only written from this thread, no need to lock
assert(!recorder->audio_disabled);
sc_mutex_lock(&recorder->mutex); sc_mutex_lock(&recorder->mutex);
@ -637,6 +651,22 @@ sc_recorder_audio_packet_sink_push(struct sc_packet_sink *sink,
return true; 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 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 audio,
@ -671,6 +701,7 @@ sc_recorder_init(struct sc_recorder *recorder, const char *filename,
recorder->video_codec = NULL; recorder->video_codec = NULL;
recorder->audio_codec = NULL; recorder->audio_codec = NULL;
recorder->audio_disabled = false;
recorder->video_stream_index = -1; recorder->video_stream_index = -1;
recorder->audio_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, .open = sc_recorder_audio_packet_sink_open,
.close = sc_recorder_audio_packet_sink_close, .close = sc_recorder_audio_packet_sink_close,
.push = sc_recorder_audio_packet_sink_push, .push = sc_recorder_audio_packet_sink_push,
.disable = sc_recorder_audio_packet_sink_disable,
}; };
recorder->audio_packet_sink.ops = &audio_ops; recorder->audio_packet_sink.ops = &audio_ops;

View file

@ -23,6 +23,14 @@ struct sc_recorder {
struct sc_packet_sink video_packet_sink; struct sc_packet_sink video_packet_sink;
struct sc_packet_sink audio_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; bool audio;
char *filename; char *filename;
@ -42,6 +50,9 @@ struct sc_recorder {
sc_cond stream_cond; sc_cond stream_cond;
const AVCodec *video_codec; const AVCodec *video_codec;
const AVCodec *audio_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 video_stream_index;
int audio_stream_index; int audio_stream_index;

View file

@ -23,6 +23,16 @@ struct sc_packet_sink_ops {
bool (*open)(struct sc_packet_sink *sink, const AVCodec *codec); bool (*open)(struct sc_packet_sink *sink, const AVCodec *codec);
void (*close)(struct sc_packet_sink *sink); void (*close)(struct sc_packet_sink *sink);
bool (*push)(struct sc_packet_sink *sink, const AVPacket *packet); 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 #endif

View file

@ -255,6 +255,10 @@ public final class AudioEncoder {
outputThread.start(); outputThread.start();
waitEnded(); waitEnded();
} catch (Throwable e) {
// Notify the client that the audio could not be captured
streamer.writeDisableStream();
throw e;
} finally { } finally {
// Cleanup everything (either at the end or on error at any step of the initialization) // Cleanup everything (either at the end or on error at any step of the initialization)
if (mediaCodecThread != null) { if (mediaCodecThread != null) {

View file

@ -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 { public void writePacket(ByteBuffer buffer, long pts, boolean config, boolean keyFrame) throws IOException {
if (config && codec == AudioCodec.OPUS) { if (config && codec == AudioCodec.OPUS) {
fixOpusConfigPacket(buffer); fixOpusConfigPacket(buffer);