Skip to content

Commit e06cfe3

Browse files
committed
feat(dave): improve DAVE decryption handling and silence frame processing
1 parent d30113e commit e06cfe3

4 files changed

Lines changed: 28 additions & 11 deletions

File tree

src/Discord/Voice/Client/Packet.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,9 @@ public function decrypt(?string $message = null): string|false|null
229229
$resultMessage = substr($stripped, $baseHeaderSize);
230230
}
231231

232-
if ($resultMessage !== false && is_callable($this->inboundFrameDecryptor)) {
232+
// Skip DAVE decryption for the standard Opus silence frame — it is never DAVE-encrypted
233+
// and passing it to libdave generates noisy "Decrypt skipping silence" C++ log spam.
234+
if ($resultMessage !== false && $resultMessage !== "\xF8\xFF\xFE" && is_callable($this->inboundFrameDecryptor)) {
233235
$resultMessage = ($this->inboundFrameDecryptor)($resultMessage, $this);
234236
}
235237

src/Discord/Voice/Dave/MediaCryptoService.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,15 @@ public function decrypt(string $frame, ?string $userId, ?int $ssrc = null): stri
116116
}
117117

118118
if ($decrypted === false) {
119-
return false;
119+
// Frame failed DAVE decryption — most likely not DAVE-encrypted yet
120+
// (user's client hasn't completed MLS handshake). Pass the frame through
121+
// as plain Opus so audio isn't silently dropped during key transitions.
122+
$this->logger->debug('DAVE decrypt returned auth failure; passing frame through as plain Opus.', [
123+
'protocol_version' => $protocolVersion,
124+
'frame_length' => strlen($frame),
125+
'ssrc' => $ssrc,
126+
]);
127+
return $frame;
120128
}
121129

122130
if ($decrypted === null) {

src/Discord/Voice/Processes/Ffmpeg.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ public static function encode(
7474
): Process {
7575
$flags = [
7676
'-protocol_whitelist', 'file,http,https,tcp,tls,crypto,pipe',
77+
'-fflags', '+nobuffer',
7778
'-i', escapeshellarg($filename ?? 'pipe:0'),
7879
'-map_metadata', '-1',
7980
'-f', 'opus',

src/Discord/Voice/VoiceClient.php

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -712,17 +712,21 @@ protected function readOggOpus(Deferred $deferred, OggStream &$ogg, int &$loops)
712712
{
713713
$this->readOpusTimer = null;
714714

715-
$loops += 1;
715+
// Record the target send time for THIS packet BEFORE the async fetch so
716+
// that the 20 ms window runs concurrently with getPacket(), not after it.
717+
$targetTime = $this->startTime + (20.0 / 1000.0) * $loops;
718+
$loops++;
716719

717-
// If the client is paused, delay by frame size and check again.
720+
// If the client is paused, insert silence and wait until the next slot.
718721
if ($this->paused) {
719722
$this->udp->insertSilence();
720-
$this->readOpusTimer = $this->discord->getLoop()->addTimer($this->frameSize / 1000, fn () => $this->readOggOpus($deferred, $ogg, $loops));
723+
$delay = max(0.0, $targetTime + (20.0 / 1000.0) - microtime(true));
724+
$this->readOpusTimer = $this->discord->getLoop()->addTimer($delay, fn () => $this->readOggOpus($deferred, $ogg, $loops));
721725

722726
return;
723727
}
724728

725-
$ogg->getPacket()->then(function ($packet) use (&$loops, &$ogg, $deferred) {
729+
$ogg->getPacket()->then(function ($packet) use (&$loops, &$ogg, $deferred, $targetTime) {
726730
// EOF for Ogg stream.
727731
if (null === $packet) {
728732
$this->reset();
@@ -731,12 +735,14 @@ protected function readOggOpus(Deferred $deferred, OggStream &$ogg, int &$loops)
731735
return;
732736
}
733737

734-
$this->udp->sendBuffer($packet);
735-
736-
$nextTime = $this->startTime + (20.0 / 1000.0) * $loops;
737-
$delay = max(0.0, $nextTime - microtime(true));
738+
$delay = max(0.0, $targetTime - microtime(true));
738739

739-
$this->readOpusTimer = $this->discord->getLoop()->addTimer($delay, fn () => $this->readOggOpus($deferred, $ogg, $loops));
740+
// Use addTimer(0) even when the deadline has already passed to avoid
741+
// unbounded recursion if several consecutive packets arrive late.
742+
$this->readOpusTimer = $this->discord->getLoop()->addTimer($delay, function () use ($packet, $deferred, &$ogg, &$loops) {
743+
$this->udp->sendBuffer($packet);
744+
$this->readOggOpus($deferred, $ogg, $loops);
745+
});
740746
}, function () use ($deferred) {
741747
$this->reset();
742748
$deferred->resolve(null);

0 commit comments

Comments
 (0)