feat: DRED (Deep REDundancy) per-channel toggle

Adds Opus 1.6 DRED support end-to-end: encoder embeds 20 ms of ML
redundancy in every packet when enabled; decoder recovers lost frames
from the next buffered packet's DRED extension rather than falling back
to PLC comfort noise.

Protocol: bool dred = 11 added to AudioConfig (backward-compatible,
defaults false). C ABI: int dred added to vc_audio_config. Encoder:
OPUS_SET_DRED_DURATION(2) when dred=true. Decoder: OpusDREDDecoder +
per-stream OpusDRED scratch pre-allocated off the RT thread;
JitterBuffer::try_copy_front_payload peeks at the next packet without
popping on every PLC step; opus_decoder_dred_decode reconstructs the
lost frame if DRED data is present, otherwise falls back to PLC.

New test: test_dred_toggle (22/22 ctest green).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-20 13:40:47 +02:00
parent de2c253199
commit fdcd8d1427
13 changed files with 412 additions and 7 deletions

View File

@@ -32,6 +32,8 @@ bool OpusEncoder::init(const OpusParams& p) {
opus_encoder_ctl(enc_, OPUS_SET_DTX(p.dtx ? 1 : 0));
opus_encoder_ctl(enc_, OPUS_SET_PACKET_LOSS_PERC(
static_cast<opus_int32>(p.expected_packet_loss)));
// DRED: 2 × 10ms frames of redundancy covers one 20ms frame loss with ML reconstruction.
opus_encoder_ctl(enc_, OPUS_SET_DRED_DURATION(p.dred ? 2 : 0));
return true;
}
@@ -70,6 +72,14 @@ int OpusDecoder::decode(const uint8_t* opus_data, int len, int16_t* out_pcm, int
return n;
}
int OpusDecoder::decode_dred(::OpusDRED* dred, int32_t dred_offset,
int16_t* out_pcm, int max_samples) {
if (!dec_ || !dred) return -1;
int n = opus_decoder_dred_decode(dec_, dred, dred_offset, out_pcm, max_samples);
if (n < 0) { err_ = opus_strerror(n); return -1; }
return n;
}
void OpusDecoder::destroy() {
if (dec_) { opus_decoder_destroy(dec_); dec_ = nullptr; }
}

View File

@@ -34,6 +34,7 @@ struct OpusParams {
uint32_t complexity = 10;
uint32_t expected_packet_loss = 0; // % 0..100
OpusApplication application = OpusApplication::Voip;
bool dred = false;
};
// Returns frame_samples for a given sample_rate + frame_ms.
@@ -95,6 +96,14 @@ class OpusDecoder {
int decode(const uint8_t* opus_data, int len, int16_t* out_pcm, int max_samples,
bool fec = false);
// Decode a lost frame using pre-parsed DRED state from the next received packet.
// dred_offset=0 means the frame immediately before the next packet.
// Returns frame_samples on success, -1 if DRED unavailable or decode failed.
#ifdef VOICECAT_HAS_OPUS
int decode_dred(::OpusDRED* dred, int32_t dred_offset, int16_t* out_pcm, int max_samples);
::OpusDecoder* raw() const { return dec_; }
#endif
void destroy();
bool valid() const { return dec_ != nullptr; }