Skip to content

Commit 5d933c8

Browse files
committed
Fix OPUS encoder timestamps
The OPUS encoder on Android rewrites the PTS so that it exactly matches the number of samples. As a consequence: - clock drift is not compensated - hard silences are ignored To fix this behavior, as a best effort, recreate the PTS based on the current time (after encoding) and the packet duration.
1 parent b292e35 commit 5d933c8

File tree

1 file changed

+28
-0
lines changed

1 file changed

+28
-0
lines changed

server/src/main/java/com/genymobile/scrcpy/audio/AudioEncoder.java

+28
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ private static class OutputTask {
5555
private final List<CodecOption> codecOptions;
5656
private final String encoderName;
5757

58+
private final boolean recreatePts;
59+
private long previousPts;
60+
5861
// Capacity of 64 is in practice "infinite" (it is limited by the number of available MediaCodec buffers, typically 4).
5962
// So many pending tasks would lead to an unacceptable delay anyway.
6063
private final BlockingQueue<InputTask> inputTasks = new ArrayBlockingQueue<>(64);
@@ -74,6 +77,10 @@ public AudioEncoder(AudioCapture capture, Streamer streamer, Options options) {
7477
this.bitRate = options.getAudioBitRate();
7578
this.codecOptions = options.getAudioCodecOptions();
7679
this.encoderName = options.getAudioEncoder();
80+
81+
// The OPUS encoder generates its own input PTS which matches the number of samples. This is not the behavior we want: it ignores any clock
82+
// drift and hard silences (packets not produced on silence). To fix this behavior, regenerate PTS based on the current time.
83+
recreatePts = streamer.getCodec() == AudioCodec.OPUS;
7784
}
7885

7986
private static MediaFormat createFormat(String mimeType, int bitRate, List<CodecOption> codecOptions) {
@@ -118,13 +125,34 @@ private void outputThread(MediaCodec mediaCodec) throws IOException, Interrupted
118125
OutputTask task = outputTasks.take();
119126
ByteBuffer buffer = mediaCodec.getOutputBuffer(task.index);
120127
try {
128+
if (recreatePts) {
129+
fixTimestamp(task.bufferInfo);
130+
}
121131
streamer.writePacket(buffer, task.bufferInfo);
122132
} finally {
123133
mediaCodec.releaseOutputBuffer(task.index, false);
124134
}
125135
}
126136
}
127137

138+
private void fixTimestamp(MediaCodec.BufferInfo bufferInfo) {
139+
assert recreatePts;
140+
141+
if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
142+
// Config packet, nothing to fix
143+
return;
144+
}
145+
146+
long pts = bufferInfo.presentationTimeUs;
147+
if (previousPts != 0) {
148+
long now = System.nanoTime() / 1000;
149+
long duration = pts - previousPts;
150+
bufferInfo.presentationTimeUs = now - duration;
151+
}
152+
153+
previousPts = pts;
154+
}
155+
128156
@Override
129157
public void start(TerminationListener listener) {
130158
thread = new Thread(() -> {

0 commit comments

Comments
 (0)