af_hdcd: give cdt expired counter a value for never set
[ffmpeg.git] / libavfilter / af_hdcd.c
index 89012eb..3626747 100644 (file)
    LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
    NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
    SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-*/
+ */
 
 /*
   Original code reverse engineered from HDCD decoder library by Christopher Key,
   which was likely reverse engineered from Windows Media Player.
-*/
+ */
 
 /*
   HDCD is High Definition Compatible Digital
@@ -818,33 +818,61 @@ static const int32_t gaintab[] = {
 
 typedef struct {
     uint64_t window;
-    unsigned char readahead, arg, control;
-    int running_gain;
-    unsigned sustain, sustain_reset;
-    int code_counterA;
-    int code_counterB;
-    int code_counterC;
-
-    /* For user information/stats, pulled up into HDCDContext
-     * by filter_frame() */
-    int count_peak_extend;
-    int count_transient_filter;
+    unsigned char readahead;
+
+    /* arg is set when a packet prefix is found.
+     * control is the active control code, where
+     * bit 0-3: target_gain, 4-bit (3.1) fixed-point value
+     * bit 4  : peak_extend
+     * bit 5  : transient_filter
+     * bit 6,7: always zero */
+    unsigned char arg, control;
+    unsigned sustain, sustain_reset; /* code detect timer */
+
+    int running_gain; /* 11-bit (3.8) fixed point, extended from target_gain */
+
+    /* counters */
+    int code_counterA;            /* 8-bit format packet */
+    int code_counterA_almost;     /* looks like an A code, but a bit expected to be 0 is 1 */
+    int code_counterB;            /* 16-bit format packet, 8-bit code, 8-bit XOR of code */
+    int code_counterB_checkfails; /* looks like a B code, but doesn't pass the XOR check */
+    int code_counterC;            /* packet prefix was found, expect a code */
+    int code_counterC_unmatched;  /* told to look for a code, but didn't find one */
+    int count_peak_extend;        /* valid packets where peak_extend was enabled */
+    int count_transient_filter;   /* valid packets where filter was detected */
     /* target_gain is a 4-bit (3.1) fixed-point value, always
      * negative, but stored positive.
      * The 16 possible values range from -7.5 to 0.0 dB in
      * steps of 0.5, but no value below -6.0 dB should appear. */
     int gain_counts[16]; /* for cursiosity, mostly */
     int max_gain;
-    int cb6, cb7; /* watch bits 6 and 7 of the control code, for curiosity */
+    /* occurences of code detect timer expiring without detecting
+     * a code. -1 for timer never set. */
+    int count_sustain_expired;
+
+    AVFilterContext *fctx; /* filter context for logging errors */
 } hdcd_state_t;
 
+typedef enum {
+    HDCD_PE_NEVER        = 0,
+    HDCD_PE_INTERMITTENT = 1,
+    HDCD_PE_PERMANENT    = 2,
+} hdcd_pe_t;
+
+static const char * const pe_str[] = {
+    "never enabled",
+    "enabled intermittently",
+    "enabled permanently"
+};
+
 typedef struct HDCDContext {
     const AVClass *class;
     hdcd_state_t state[2];
 
     /* User information/stats */
     int hdcd_detected;
-    int uses_peak_extend;
+    int det_errors;            /* detectable errors */
+    hdcd_pe_t peak_extend;
     int uses_transient_filter; /* detected, but not implemented */
     float max_gain_adjustment; /* in dB, expected in the range -6.0 to 0.0 */
 } HDCDContext;
@@ -872,18 +900,30 @@ static void hdcd_reset(hdcd_state_t *state, unsigned rate)
     state->sustain_reset = rate * 10;
 
     state->code_counterA = 0;
+    state->code_counterA_almost = 0;
     state->code_counterB = 0;
+    state->code_counterB_checkfails = 0;
     state->code_counterC = 0;
+    state->code_counterC_unmatched = 0;
 
     state->count_peak_extend = 0;
     state->count_transient_filter = 0;
     for(i = 0; i < 16; i++) state->gain_counts[i] = 0;
     state->max_gain = 0;
-    state->cb6 = 0;
-    state->cb7 = 0;
+
+    state->count_sustain_expired = -1;
 }
 
-static int integrate(hdcd_state_t *state, int *flag, const int32_t *samples, int count, int stride)
+/* update the user info/counters */
+static void hdcd_update_info(hdcd_state_t *state)
+{
+    if (state->control & 16) state->count_peak_extend++;
+    if (state->control & 32) state->count_transient_filter++;
+    state->gain_counts[state->control & 15]++;
+    state->max_gain = FFMAX(state->max_gain, (state->control & 15));
+}
+
+static int hdcd_integrate(hdcd_state_t *state, int *flag, const int32_t *samples, int count, int stride)
 {
     uint32_t bits = 0;
     int result = FFMIN(state->readahead, count);
@@ -903,16 +943,41 @@ static int integrate(hdcd_state_t *state, int *flag, const int32_t *samples, int
     bits = (state->window ^ state->window >> 5 ^ state->window >> 23);
 
     if (state->arg) {
-        if ((bits & 0xffffffc8) == 0x0fa00500) {
-            state->control = (bits & 255) + (bits & 7);
-            *flag = 1;
-            state->code_counterA++;
-        }
-        if (((bits ^ (~bits >> 8 & 255)) & 0xffff00ff) == 0xa0060000) {
-            state->control = bits >> 8 & 255;
-            *flag = 1;
-            state->code_counterB++;
+        if ((bits & 0x0fa00500) == 0x0fa00500) {
+            /* A: 8-bit code */
+            if ((bits & 0xc8) == 0) {
+                /*                   [..pt gggg]
+                 * 0x0fa005[..] -> 0b[00.. 0...], gain part doubled */
+                state->control = (bits & 255) + (bits & 7);
+                *flag = 1;
+                state->code_counterA++;
+            } else {
+                /* one of bits 3, 6, or 7 was not 0 */
+                state->code_counterA_almost++;
+                av_log(state->fctx, AV_LOG_VERBOSE,
+                    "hdcd error: Control A almost: 0x%08x\n", bits);
+            }
+        } else if ((bits & 0xa0060000) == 0xa0060000) {
+            /* B: 8-bit code, 8-bit XOR check */
+            if (((bits ^ (~bits >> 8 & 255)) & 0xffff00ff) == 0xa0060000) {
+                /*          check:   [..pt gggg ~(..pt gggg)]
+                 * 0xa006[....] -> 0b[.... ....   .... .... ] */
+                state->control = bits >> 8 & 255;
+                *flag = 1;
+                state->code_counterB++;
+            } else {
+                /* XOR check failed */
+                state->code_counterB_checkfails++;
+                av_log(state->fctx, AV_LOG_VERBOSE,
+                    "hdcd error: Control B check failed: 0x%08x\n", bits);
+            }
+        } else {
+            /* told to look for a code, but didn't match one */
+            state->code_counterC_unmatched++;
+            av_log(state->fctx, AV_LOG_VERBOSE,
+                "hdcd error: Unmatched code: 0x%08x\n", bits);
         }
+        if (*flag) hdcd_update_info(state);
         state->arg = 0;
     }
     if (bits == 0x7e0fa005 || bits == 0x7e0fa006) {
@@ -928,10 +993,22 @@ static int integrate(hdcd_state_t *state, int *flag, const int32_t *samples, int
     return result;
 }
 
+static void hdcd_sustain_reset(hdcd_state_t *state)
+{
+    state->sustain = state->sustain_reset;
+    /* if this is the first reset then change
+     * from never set, to never expired */
+    if (state->count_sustain_expired == -1)
+        state->count_sustain_expired = 0;
+}
+
 static int hdcd_scan(hdcd_state_t *state, const int32_t *samples, int max, int stride)
 {
+    int cdt_active = 0;
+    /* code detect timer */
     int result;
     if (state->sustain > 0) {
+        cdt_active = 1;
         if (state->sustain <= max) {
             state->control = 0;
             max = state->sustain;
@@ -941,14 +1018,18 @@ static int hdcd_scan(hdcd_state_t *state, const int32_t *samples, int max, int s
     result = 0;
     while (result < max) {
         int flag;
-        int consumed = integrate(state, &flag, samples, max - result, stride);
+        int consumed = hdcd_integrate(state, &flag, samples, max - result, stride);
         result += consumed;
         if (flag > 0) {
-            state->sustain = state->sustain_reset;
+            /* reset timer if code detected in channel */
+            hdcd_sustain_reset(state);
             break;
         }
         samples += consumed * stride;
     }
+    /* code detect timer expired */
+    if (cdt_active && state->sustain == 0)
+        state->count_sustain_expired++;
     return result;
 }
 
@@ -1011,18 +1092,6 @@ static int hdcd_envelope(int32_t *samples, int count, int stride, int gain, int
     return gain;
 }
 
-/* update the user info/flags */
-static void hdcd_update_info(hdcd_state_t *state)
-{
-    if (state->control & 16) state->count_peak_extend++;
-    if (state->control & 32) state->count_transient_filter++;
-    state->gain_counts[state->control & 15]++;
-    state->max_gain = FFMAX(state->max_gain, (state->control & 15));
-
-    if (state->control & 64) state->cb6++;
-    if (state->control & 128) state->cb7++;
-}
-
 static void hdcd_process(hdcd_state_t *state, int32_t *samples, int count, int stride)
 {
     int32_t *samples_end = samples + count * stride;
@@ -1031,8 +1100,6 @@ static void hdcd_process(hdcd_state_t *state, int32_t *samples, int count, int s
     int target_gain = (state->control & 15) << 7;
     int lead = 0;
 
-    hdcd_update_info(state);
-
     while (count > lead) {
         int envelope_run;
         int run;
@@ -1049,7 +1116,6 @@ static void hdcd_process(hdcd_state_t *state, int32_t *samples, int count, int s
         lead = run - envelope_run;
         peak_extend = (state->control & 16);
         target_gain = (state->control & 15) << 7;
-        hdcd_update_info(state);
     }
     if (lead > 0) {
         av_assert0(samples + lead * stride <= samples_end);
@@ -1072,6 +1138,7 @@ static int filter_frame(AVFilterLink *inlink, AVFrame *in)
     const int16_t *in_data;
     int32_t *out_data;
     int n, c;
+    int detect, packets, pe_packets;
 
     out = ff_get_audio_buffer(outlink, in->nb_samples);
     if (!out) {
@@ -1087,15 +1154,34 @@ static int filter_frame(AVFilterLink *inlink, AVFrame *in)
         out_data[n] = in_data[n];
     }
 
+    detect = 0;
+    packets = 0;
+    pe_packets = 0;
+    s->det_errors = 0;
     for (c = 0; c < inlink->channels; c++) {
         hdcd_state_t *state = &s->state[c];
         hdcd_process(state, out_data + c, in->nb_samples, out->channels);
-
-        s->uses_peak_extend |= !!state->count_peak_extend;
+        if (state->sustain) detect++;
+        packets += state->code_counterA + state->code_counterB;
+        pe_packets += state->count_peak_extend;
         s->uses_transient_filter |= !!state->count_transient_filter;
         s->max_gain_adjustment = FFMIN(s->max_gain_adjustment, GAINTOFLOAT(state->max_gain));
-        s->hdcd_detected |= state->code_counterC || state->code_counterB || state->code_counterA;
+        s->det_errors += state->code_counterA_almost
+            + state->code_counterB_checkfails
+            + state->code_counterC_unmatched;
+    }
+    if (pe_packets) {
+        /* if every valid packet has used PE, call it permanent */
+        if (packets == pe_packets)
+            s->peak_extend = HDCD_PE_PERMANENT;
+        else
+            s->peak_extend = HDCD_PE_INTERMITTENT;
+    } else {
+        s->peak_extend = HDCD_PE_NEVER;
     }
+    /* HDCD is detected if a valid packet is active in all (both)
+     * channels at the same time. */
+    if (detect == inlink->channels) s->hdcd_detected = 1;
 
     av_frame_free(&in);
     return ff_filter_frame(outlink, out);
@@ -1155,23 +1241,31 @@ static av_cold void uninit(AVFilterContext *ctx)
     /* dump the state for each channel for AV_LOG_VERBOSE */
     for (i = 0; i < 2; i++) {
         hdcd_state_t *state = &s->state[i];
-        av_log(ctx, AV_LOG_VERBOSE, "Channel %d: counter A: %d, B: %d, C: %d\n", i, state->code_counterA,
-                state->code_counterB, state->code_counterC);
-        av_log(ctx, AV_LOG_VERBOSE, "Channel %d: c(pe): %d, c(tf): %d, cb6: %d, cb7: %d\n", i,
-                state->count_peak_extend, state->count_transient_filter, state->cb6, state->cb7);
+        av_log(ctx, AV_LOG_VERBOSE, "Channel %d: counter A: %d, B: %d, C: %d\n", i,
+                state->code_counterA, state->code_counterB, state->code_counterC);
+        av_log(ctx, AV_LOG_VERBOSE, "Channel %d: pe: %d, tf: %d, almost_A: %d, checkfail_B: %d, unmatched_C: %d, cdt_expired: %d\n", i,
+            state->count_peak_extend,
+            state->count_transient_filter,
+            state->code_counterA_almost,
+            state->code_counterB_checkfails,
+            state->code_counterC_unmatched,
+            state->count_sustain_expired);
         for (j = 0; j <= state->max_gain; j++) {
-            av_log(ctx, AV_LOG_VERBOSE, "Channel %d: tg %0.1f - %d\n", i, GAINTOFLOAT(j), state->gain_counts[j]);
+            av_log(ctx, AV_LOG_VERBOSE, "Channel %d: tg %0.1f: %d\n", i, GAINTOFLOAT(j), state->gain_counts[j]);
         }
     }
 
     /* log the HDCD decode information */
-    av_log(ctx, AV_LOG_INFO,
-        "HDCD detected: %s, peak_extend: %s, max_gain_adj: %0.1f dB, transient_filter: %s\n",
-        (s->hdcd_detected) ? "yes" : "no",
-        (s->uses_peak_extend) ? "enabled" : "never enabled",
-        s->max_gain_adjustment,
-        (s->uses_transient_filter) ? "detected" : "not detected"
-        );
+    if (s->hdcd_detected)
+        av_log(ctx, AV_LOG_INFO,
+            "HDCD detected: yes, peak_extend: %s, max_gain_adj: %0.1f dB, transient_filter: %s, detectable errors: %d%s\n",
+            pe_str[s->peak_extend],
+            s->max_gain_adjustment,
+            (s->uses_transient_filter) ? "detected" : "not detected",
+            s->det_errors, (s->det_errors) ? " (try -v verbose)" : ""
+            );
+    else
+        av_log(ctx, AV_LOG_INFO, "HDCD detected: no\n");
 }
 
 static av_cold int init(AVFilterContext *ctx)
@@ -1184,6 +1278,7 @@ static av_cold int init(AVFilterContext *ctx)
 
     for (c = 0; c < 2; c++) {
         hdcd_reset(&s->state[c], 44100);
+        s->state[c].fctx = ctx;
     }
 
     return 0;