mirror of
https://github.com/Genymobile/scrcpy.git
synced 2025-04-22 04:25:01 +00:00
Disable audio if device could not capture
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.
This commit is contained in:
parent
3038bf4c3b
commit
166257b808
6 changed files with 94 additions and 11 deletions
|
@ -160,6 +160,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;
|
||||
|
@ -174,6 +184,13 @@ run_demuxer(void *data) {
|
|||
goto end;
|
||||
}
|
||||
|
||||
if (raw_codec_id == 0) {
|
||||
// Stream explicitly disabled by the device
|
||||
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
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
@ -582,6 +590,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);
|
||||
|
@ -596,6 +606,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
|
||||
|
@ -610,6 +622,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);
|
||||
|
||||
|
@ -635,6 +649,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 disabled: it could not be captured by the device");
|
||||
|
||||
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,
|
||||
|
@ -669,6 +699,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;
|
||||
|
@ -694,6 +725,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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -200,19 +200,24 @@ public final class AudioEncoder {
|
|||
|
||||
@TargetApi(Build.VERSION_CODES.M)
|
||||
public void encode() throws IOException {
|
||||
mediaCodec = MediaCodec.createEncoderByType(MIMETYPE); // may throw IOException
|
||||
|
||||
try {
|
||||
recorder = createAudioRecord();
|
||||
try {
|
||||
mediaCodec = MediaCodec.createEncoderByType(MIMETYPE); // may throw IOException
|
||||
recorder = createAudioRecord();
|
||||
|
||||
mediaCodecThread = new HandlerThread("AudioEncoder");
|
||||
mediaCodecThread.start();
|
||||
mediaCodecThread = new HandlerThread("AudioEncoder");
|
||||
mediaCodecThread.start();
|
||||
|
||||
MediaFormat format = createFormat();
|
||||
mediaCodec.setCallback(new EncoderCallback(), new Handler(mediaCodecThread.getLooper()));
|
||||
mediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
|
||||
MediaFormat format = createFormat();
|
||||
mediaCodec.setCallback(new EncoderCallback(), new Handler(mediaCodecThread.getLooper()));
|
||||
mediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
|
||||
|
||||
recorder.startRecording();
|
||||
recorder.startRecording();
|
||||
} catch (Throwable e) {
|
||||
// Notify the client that the audio could not be captured
|
||||
streamer.writeDisableStream();
|
||||
throw e;
|
||||
}
|
||||
|
||||
inputThread = new Thread(() -> {
|
||||
try {
|
||||
|
@ -243,7 +248,9 @@ public final class AudioEncoder {
|
|||
inputThread.start();
|
||||
outputThread.start();
|
||||
} catch (Throwable e) {
|
||||
mediaCodec.release();
|
||||
if (mediaCodec != null) {
|
||||
mediaCodec.release();
|
||||
}
|
||||
if (recorder != null) {
|
||||
recorder.release();
|
||||
}
|
||||
|
|
|
@ -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 codecBuffer, MediaCodec.BufferInfo bufferInfo) throws IOException {
|
||||
if (codec == AudioCodec.OPUS) {
|
||||
fixOpusConfigPacket(codecBuffer, bufferInfo);
|
||||
|
|
Loading…
Add table
Reference in a new issue