Compare commits

..

6 Commits

Author SHA1 Message Date
Romain Vimont
63d848fc55 Fix PTS produced by the default OPUS encoder
The default OPUS encoder on Android rewrites the PTS so that it exactly
matches the number of samples.

As a consequence:
 - audio clock drift is not compensated
 - hard silences are ignored

To fix this behavior, recreate the PTS based on the current time (after
encoding) and the packet duration.
2025-03-02 18:00:12 +01:00
Romain Vimont
b292e356de Add audio sources 2025-03-02 17:17:01 +01:00
Romain Vimont
9fb7446b88 Refactor audio sources
Store the target audio source integer (one of the constants in
android.media.MediaRecorder.AudioSource) in the AudioSource enum (or -1
if not relevant).

This will simplify adding new audio sources.
2025-02-22 12:46:06 +01:00
Romain Vimont
671025cb68 Handle audio stream discontinuities
The audio regulator assumed a continuous audio stream. But some audio
sources (like the "voice call" audio source) do not produce any packets
on silence, breaking this assumption.

Use PTS to detect such discontinuities.

TODO: if PTS values are broken, the detection is also broken.
2025-02-22 12:46:06 +01:00
Romain Vimont
8925bdc8fd Report underflow samples in verbose mode
Report the number of silence samples inserted due to underflow every
second, along with the other metrics.
2025-02-22 12:26:27 +01:00
Romain Vimont
ea4c076345 Disable audio regulator underflow logs
Only enable them if SC_AUDIO_REGULATOR_DEBUG is set, as they may spam
the output.
2025-02-22 12:26:27 +01:00
4 changed files with 7 additions and 12 deletions

View File

@@ -160,7 +160,6 @@ sc_audio_regulator_push(struct sc_audio_regulator *ar, const AVFrame *frame) {
// Reset state
ar->avg_buffering.avg = ar->target_buffering;
int ret = swr_set_compensation(swr_ctx, 0, 0);
(void) ret;
assert(!ret); // disabling compensation should never fail
ar->compensation_active = false;
ar->samples_since_resync = 0;

View File

@@ -142,7 +142,6 @@ public final class AudioEncoder implements AsyncProcessor {
long pts = bufferInfo.presentationTimeUs;
if (previousPts != 0) {
long now = System.nanoTime() / 1000;
// This specific encoder produces PTS matching the exact number of samples
long duration = pts - previousPts;
bufferInfo.presentationTimeUs = now - duration;
}
@@ -219,11 +218,10 @@ public final class AudioEncoder implements AsyncProcessor {
Codec codec = streamer.getCodec();
mediaCodec = createMediaCodec(codec, encoderName);
// The default OPUS and FLAC encoders overwrite the input PTS with a value that matches the number of samples. This is not the behavior
// we want: it ignores any audio clock drift and hard silences (packets not produced on silence). To work around this behavior,
// regenerate PTS based on the current time and the packet duration.
String codecName = mediaCodec.getCanonicalName();
recreatePts = "c2.android.opus.encoder".equals(codecName) || "c2.android.flac.encoder".equals(codecName);
// The default OPUS encoder generates its own input PTS which matches the number of samples. This is not the behavior we want: it
// ignores any audio clock drift and hard silences (packets not produced on silence). To fix this behavior, regenerate PTS based on the
// current time and the packet duration.
recreatePts = "c2.android.opus.encoder".equals(mediaCodec.getName());
mediaCodecThread = new HandlerThread("media-codec");
mediaCodecThread.start();

View File

@@ -13,8 +13,8 @@ public enum AudioSource {
MIC_VOICE_RECOGNITION("mic-voice-recognition", MediaRecorder.AudioSource.VOICE_RECOGNITION),
MIC_VOICE_COMMUNICATION("mic-voice-communication", MediaRecorder.AudioSource.VOICE_COMMUNICATION),
VOICE_CALL("voice-call", MediaRecorder.AudioSource.VOICE_CALL),
VOICE_CALL_UPLINK("voice-call-uplink", MediaRecorder.AudioSource.VOICE_UPLINK),
VOICE_CALL_DOWNLINK("voice-call-downlink", MediaRecorder.AudioSource.VOICE_DOWNLINK),
VOICE_CALL_UPLINK("voice-call-uplink", MediaRecorder.AudioSource.VOICE_CALL),
VOICE_CALL_DOWNLINK("voice-call-downlink", MediaRecorder.AudioSource.VOICE_CALL),
VOICE_PERFORMANCE("voice-performance", MediaRecorder.AudioSource.VOICE_PERFORMANCE);
private final String name;

View File

@@ -23,9 +23,7 @@ public class DisplaySizeMonitor {
// On Android 14, DisplayListener may be broken (it never sends events). This is fixed in recent Android 14 upgrades, but we can't really
// detect it directly, so register a DisplayWindowListener (introduced in Android 11) to listen to configuration changes instead.
// It has been broken again after an Android 15 upgrade: <https://github.com/Genymobile/scrcpy/issues/5908>
// So use the default method only before Android 14.
private static final boolean USE_DEFAULT_METHOD = Build.VERSION.SDK_INT < AndroidVersions.API_34_ANDROID_14;
private static final boolean USE_DEFAULT_METHOD = Build.VERSION.SDK_INT != AndroidVersions.API_34_ANDROID_14;
private DisplayManager.DisplayListenerHandle displayListenerHandle;
private HandlerThread handlerThread;