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:
parent
7de0622214
commit
13a3395a33
6 changed files with 90 additions and 3 deletions
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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);
|
||||||
|
|
Loading…
Reference in a new issue