From 3e2a6b6253fe7a514c50100e4144c52c065a7fa1 Mon Sep 17 00:00:00 2001
From: "Timothy B. Terriberry" <tterribe@xiph.org>
Date: Thu, 22 Feb 2024 06:12:55 -0800
Subject: [PATCH] Add signaling for a maximum DRED quantizer.

Since any value of dQ > 0 will cause the initial quantizer to
 degrade to the format-implied maximum (15) with a sufficient
 number of DRED frames, allow signaling a maximum smaller than 15.
This allows encoders to improve the minimum quality of long DRED
 sequences (at the expense of bitrate) without requiring a constant
 quantizer for all frames (dQ == 0).
---
 silk/dred_coding.c  |  4 ++--
 silk/dred_coding.h  |  2 +-
 silk/dred_decoder.c | 26 ++++++++++++++++++++++++--
 silk/dred_encoder.c | 13 +++++++++++--
 silk/dred_encoder.h |  2 +-
 src/opus_encoder.c  | 13 ++++++++-----
 6 files changed, 47 insertions(+), 13 deletions(-)

diff --git a/silk/dred_coding.c b/silk/dred_coding.c
index d702fa322..669ddc413 100644
--- a/silk/dred_coding.c
+++ b/silk/dred_coding.c
@@ -36,9 +36,9 @@
 #include "dred_config.h"
 #include "dred_coding.h"
 
-int compute_quantizer(int q0, int dQ, int i) {
+int compute_quantizer(int q0, int dQ, int qmax, int i) {
   int quant;
   static const int dQ_table[8] = {0, 2, 3, 4, 6, 8, 12, 16};
   quant = q0 + (dQ_table[dQ]*i + 8)/16;
-  return quant > 15 ? 15 : quant;
+  return quant > qmax ? qmax : quant;
 }
diff --git a/silk/dred_coding.h b/silk/dred_coding.h
index 0a5ddb61f..1ce040c23 100644
--- a/silk/dred_coding.h
+++ b/silk/dred_coding.h
@@ -31,6 +31,6 @@
 #include "opus_types.h"
 #include "entcode.h"
 
-int compute_quantizer(int q0, int dQ, int i);
+int compute_quantizer(int q0, int dQ, int qmax, int i);
 
 #endif
diff --git a/silk/dred_decoder.c b/silk/dred_decoder.c
index fefbf41d6..1b284330d 100644
--- a/silk/dred_decoder.c
+++ b/silk/dred_decoder.c
@@ -57,6 +57,7 @@ int dred_ec_decode(OpusDRED *dec, const opus_uint8 *bytes, int num_bytes, int mi
   int offset;
   int q0;
   int dQ;
+  int qmax;
   int state_qoffset;
   int extra_offset;
 
@@ -72,7 +73,28 @@ int dred_ec_decode(OpusDRED *dec, const opus_uint8 *bytes, int num_bytes, int mi
   /* Compute total offset, including DRED position in a multiframe packet. */
   dec->dred_offset = 16 - ec_dec_uint(&ec, 32) - extra_offset + dred_frame_offset;
   /*printf("%d %d %d\n", dred_offset, q0, dQ);*/
-
+  qmax = 15;
+  if (q0 < 14 && dQ > 0) {
+    int nvals;
+    int ft;
+    int s;
+    /* The distribution for the dQmax symbol is split evenly between zero
+        (which implies qmax == 15) and larger values, with the probability of
+        all larger values being uniform.
+       This is equivalent to coding 1 bit to decide if the maximum is less than
+        15 followed by a uint to decide the actual value if it is less than
+        15, but combined into a single symbol. */
+    nvals = 15 - (q0 + 1);
+    ft = 2*nvals;
+    s = ec_decode(&ec, ft);
+    if (s >= nvals) {
+      qmax = q0 + (s - nvals) + 1;
+      ec_dec_update(&ec, s, s + 1, ft);
+    }
+    else {
+      ec_dec_update(&ec, 0, nvals, ft);
+    }
+  }
   state_qoffset = q0*DRED_STATE_DIM;
   dred_decode_latents(
       &ec,
@@ -88,7 +110,7 @@ int dred_ec_decode(OpusDRED *dec, const opus_uint8 *bytes, int num_bytes, int mi
       /* FIXME: Figure out how to avoid missing a last frame that would take up < 8 bits. */
       if (8*num_bytes - ec_tell(&ec) <= 7)
          break;
-      q_level = compute_quantizer(q0, dQ, i/2);
+      q_level = compute_quantizer(q0, dQ, qmax, i/2);
       offset = q_level*DRED_LATENT_DIM;
       dred_decode_latents(
           &ec,
diff --git a/silk/dred_encoder.c b/silk/dred_encoder.c
index 804a67abe..c3502bf32 100644
--- a/silk/dred_encoder.c
+++ b/silk/dred_encoder.c
@@ -257,7 +257,7 @@ static int dred_voice_active(const unsigned char *activity_mem, int offset) {
     return 0;
 }
 
-int dred_encode_silk_frame(DREDEnc *enc, unsigned char *buf, int max_chunks, int max_bytes, int q0, int dQ, unsigned char *activity_mem, int arch) {
+int dred_encode_silk_frame(DREDEnc *enc, unsigned char *buf, int max_chunks, int max_bytes, int q0, int dQ, int qmax, unsigned char *activity_mem, int arch) {
     ec_enc ec_encoder;
 
     int q_level;
@@ -301,6 +301,15 @@ int dred_encode_silk_frame(DREDEnc *enc, unsigned char *buf, int max_chunks, int
        ec_enc_uint(&ec_encoder, 0, 2);
        ec_enc_uint(&ec_encoder, total_offset, 32);
     }
+    celt_assert(qmax >= q0);
+    if (q0 < 14 && dQ > 0) {
+      int nvals;
+      /* If you want to use qmax == q0, you should have set dQ = 0. */
+      celt_assert(qmax > q0);
+      nvals = 15 - (q0 + 1);
+      ec_encode(&ec_encoder, qmax >= 15 ? 0 : nvals + qmax - (q0 + 1),
+        qmax >= 15 ? nvals : nvals + qmax - q0, 2*nvals);
+    }
     state_qoffset = q0*DRED_STATE_DIM;
     dred_encode_latents(
         &ec_encoder,
@@ -318,7 +327,7 @@ int dred_encode_silk_frame(DREDEnc *enc, unsigned char *buf, int max_chunks, int
     for (i = 0; i < IMIN(2*max_chunks, enc->latents_buffer_fill-latent_offset-1); i += 2)
     {
         int active;
-        q_level = compute_quantizer(q0, dQ, i/2);
+        q_level = compute_quantizer(q0, dQ, qmax, i/2);
         offset = q_level * DRED_LATENT_DIM;
 
         dred_encode_latents(
diff --git a/silk/dred_encoder.h b/silk/dred_encoder.h
index 137c963dd..dd2410499 100644
--- a/silk/dred_encoder.h
+++ b/silk/dred_encoder.h
@@ -66,6 +66,6 @@ void dred_deinit_encoder(DREDEnc *enc);
 
 void dred_compute_latents(DREDEnc *enc, const float *pcm, int frame_size, int extra_delay, int arch);
 
-int dred_encode_silk_frame(DREDEnc *enc, unsigned char *buf, int max_chunks, int max_bytes, int q0, int dQ, unsigned char *activity_mem, int arch);
+int dred_encode_silk_frame(DREDEnc *enc, unsigned char *buf, int max_chunks, int max_bytes, int q0, int dQ, int qmax, unsigned char *activity_mem, int arch);
 
 #endif
diff --git a/src/opus_encoder.c b/src/opus_encoder.c
index e9148a374..4c76182c9 100644
--- a/src/opus_encoder.c
+++ b/src/opus_encoder.c
@@ -131,6 +131,7 @@ struct OpusEncoder {
     int          dred_duration;
     int          dred_q0;
     int          dred_dQ;
+    int          dred_qmax;
     int          dred_target_chunks;
     unsigned char activity_mem[DRED_MAX_FRAMES*4]; /* 2.5ms resolution*/
 #endif
@@ -571,7 +572,7 @@ OpusEncoder *opus_encoder_create(opus_int32 Fs, int channels, int application, i
 #ifdef ENABLE_DRED
 
 static const float dred_bits_table[16] = {73.2f, 68.1f, 62.5f, 57.0f, 51.5f, 45.7f, 39.9f, 32.4f, 26.4f, 20.4f, 16.3f, 13.f, 9.3f, 8.2f, 7.2f, 6.4f};
-static int estimate_dred_bitrate(int q0, int dQ, int duration, opus_int32 target_bits, int *target_chunks) {
+static int estimate_dred_bitrate(int q0, int dQ, int qmax, int duration, opus_int32 target_bits, int *target_chunks) {
    int dred_chunks;
    int i;
    float bits;
@@ -582,7 +583,7 @@ static int estimate_dred_bitrate(int q0, int dQ, int duration, opus_int32 target
    dred_chunks = IMIN((duration+5)/4, DRED_NUM_REDUNDANCY_FRAMES/2);
    if (target_chunks != NULL) *target_chunks = 0;
    for (i=0;i<dred_chunks;i++) {
-      int q = compute_quantizer(q0, dQ, i);
+      int q = compute_quantizer(q0, dQ, qmax, i);
       bits += dred_bits_table[q];
       if (target_chunks != NULL && bits < target_bits) *target_chunks = i+1;
    }
@@ -597,7 +598,7 @@ static opus_int32 compute_dred_bitrate(OpusEncoder *st, opus_int32 bitrate_bps,
    opus_int32 target_dred_bitrate;
    int target_chunks;
    opus_int32 max_dred_bits;
-   int q0, dQ;
+   int q0, dQ, qmax;
    if (st->silk_mode.useInBandFEC) {
       dred_frac = MIN16(.7f, 3.f*st->silk_mode.packetLossPercentage/100.f);
       bitrate_offset = 20000;
@@ -614,10 +615,11 @@ static opus_int32 compute_dred_bitrate(OpusEncoder *st, opus_int32 bitrate_bps,
    /* Approximate fit based on a few experiments. Could probably be improved. */
    q0 = IMIN(15, IMAX(4, 51 - 3*EC_ILOG(IMAX(1, bitrate_bps-bitrate_offset))));
    dQ = bitrate_bps-bitrate_offset > 36000 ? 3 : 5;
+   qmax = 15;
    target_dred_bitrate = IMAX(0, (int)(dred_frac*(bitrate_bps-bitrate_offset)));
    if (st->dred_duration > 0) {
       opus_int32 target_bits = target_dred_bitrate*frame_size/st->Fs;
-      max_dred_bits = estimate_dred_bitrate(q0, dQ, st->dred_duration, target_bits, &target_chunks);
+      max_dred_bits = estimate_dred_bitrate(q0, dQ, qmax, st->dred_duration, target_bits, &target_chunks);
    } else {
       max_dred_bits = 0;
       target_chunks=0;
@@ -628,6 +630,7 @@ static opus_int32 compute_dred_bitrate(OpusEncoder *st, opus_int32 bitrate_bps,
       dred_bitrate = 0;
    st->dred_q0 = q0;
    st->dred_dQ = dQ;
+   st->dred_qmax = qmax;
    st->dred_target_chunks = target_chunks;
    return dred_bitrate;
 }
@@ -2419,7 +2422,7 @@ static opus_int32 opus_encode_frame_native(OpusEncoder *st, const opus_val16 *pc
            buf[1] = DRED_EXPERIMENTAL_VERSION;
 #endif
            dred_bytes = dred_encode_silk_frame(&st->dred_encoder, buf+DRED_EXPERIMENTAL_BYTES, dred_chunks, dred_bytes_left-DRED_EXPERIMENTAL_BYTES,
-                                               st->dred_q0, st->dred_dQ, st->activity_mem, st->arch);
+                                               st->dred_q0, st->dred_dQ, st->dred_qmax, st->activity_mem, st->arch);
            if (dred_bytes > 0) {
               dred_bytes += DRED_EXPERIMENTAL_BYTES;
               celt_assert(dred_bytes <= dred_bytes_left);
-- 
GitLab