[FFmpeg-devel] [PATCH] af_hdcd: Process stereo channels together, fix #5727

Burt P pburt0 at gmail.com
Mon Jul 25 21:54:36 EEST 2016


Issue #5727: gain adjustment should only be applied if matching
gain value from a valid packet in both channels. The existing functions process
each channel separately, so it was not possible.

* New versions of hdcd_process(), hdcd_scan(), hdcd_integrate() named
  hdcd_*_stereo() that process both channels together.
* target_gain applied will be the last matching target_gain.
* The old single channel functions remain as an option. They can be used
  by filter option: -af hdcd=process_stereo=0.
* Some code common to both sets has been moved into own functions:
  hdcd_code(), hdcd_control(), hdcd_sustain_reset().
* This is against master, so it also includes the changes in an earlier
  patch set for HDCD detection, PE mode, cdt counter, and code comments.

Signed-off-by: Burt P <pburt0 at gmail.com>
---
 libavfilter/af_hdcd.c | 463 +++++++++++++++++++++++++++++++++++++++++---------
 1 file changed, 386 insertions(+), 77 deletions(-)

diff --git a/libavfilter/af_hdcd.c b/libavfilter/af_hdcd.c
index 6f0db71..725b697 100644
--- a/libavfilter/af_hdcd.c
+++ b/libavfilter/af_hdcd.c
@@ -23,12 +23,12 @@
    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,44 +818,88 @@ 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_counterA_almost; /* looks like an A code, but a bit expected to be 0 is 1 */
-    int code_counterB;
+    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;
-    int code_counterC_unmatched; /* told to look for a code, but didn't find one */
-
-    /* For user information/stats, pulled up into HDCDContext
-     * by filter_frame() */
-    int count_peak_extend;
-    int count_transient_filter;
+    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;
-
-    AVFilterContext *fctx; /* filter context for logging errors */
+    /* occurences of code detect timer expiring without detecting
+     * a code. -1 for timer never set. */
+    int count_sustain_expired;
 } hdcd_state_t;
 
+typedef enum {
+    HDCD_PE_NEVER        =0,
+    HDCD_PE_INTERMITTENT =1,
+    HDCD_PE_PERMANENT    =2
+} hdcd_pe_t;
+
+const char* pe_str[] = {
+    "never enabled",
+    "enabled intermittently",
+    "enabled permanently"
+    };
+
+#define HDCD_PROCESS_STEREO_DEFAULT 1
+#define HDCD_MAX_CHANNELS 2
+
 typedef struct HDCDContext {
     const AVClass *class;
-    hdcd_state_t state[2];
+    hdcd_state_t state[HDCD_MAX_CHANNELS];
+
+    /* use hdcd_*_stereo() functions to process both channels together.
+     * -af hdcd=process_stereo=0 for off
+     * -af hdcd=process_stereo=1 for on
+     * default is HDCD_PROCESS_STEREO_DEFAULT */
+    int process_stereo;
+    /* always extend peaks above -3dBFS even if PE isn't signaled
+     * -af hdcd=force_pe=0 for off
+     * -af hdcd=force_pe=1 for on
+     * default is off */
+    int force_pe;
+
+    AVFilterContext *fctx; /* filter context for logging errors */
+    int sample_count;      /* used in error logging */
+    int val_target_gain;   /* last matching target_gain in both channels */
 
     /* User information/stats */
     int hdcd_detected;
     int det_errors;            /* detectable errors */
-    int uses_peak_extend;
+    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;
 
+#define OFFSET(x) offsetof(HDCDContext, x)
 static const AVOption hdcd_options[] = {
-     {NULL}
+    { "process_stereo", "Process stereo channels together. Only apply target_gain when both channels match.",
+        OFFSET(process_stereo), AV_OPT_TYPE_INT, { .i64 = HDCD_PROCESS_STEREO_DEFAULT }, 0, 1, 0 },
+    { "force_pe", "Always extend peaks above -3dBFS even when PE is not signaled.",
+        OFFSET(force_pe), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, 0 },
+    {NULL}
 };
 
 AVFILTER_DEFINE_CLASS(hdcd);
@@ -882,11 +926,11 @@ static void hdcd_reset(hdcd_state_t *state, unsigned rate)
     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->count_sustain_expired = -1;
 }
 
 /* update the user info/counters */
@@ -898,7 +942,46 @@ static void hdcd_update_info(hdcd_state_t *state)
     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)
+typedef enum {
+    HDCD_CODE_NONE=0,
+    HDCD_CODE_A,
+    HDCD_CODE_A_ALMOST,
+    HDCD_CODE_B,
+    HDCD_CODE_B_CHECKFAIL,
+    HDCD_CODE_EXPECT_A,
+    HDCD_CODE_EXPECT_B,
+} hdcd_code_result_t;
+
+static hdcd_code_result_t hdcd_code(const uint32_t bits, unsigned char *code)
+{
+    if ((bits & 0x0fa00500) == 0x0fa00500) {
+        /* A: 8-bit code  0x7e0fa005[..] */
+        if ((bits & 0xc8) == 0) {
+            /*                   [..pt gggg]
+             * 0x0fa005[..] -> 0b[00.. 0...], gain part doubled */
+            *code = (bits & 255) + (bits & 7);
+            return HDCD_CODE_A;
+        } else
+            return HDCD_CODE_A_ALMOST; /* one of bits 3, 6, or 7 was not 0 */
+    } else if ((bits & 0xa0060000) == 0xa0060000) {
+        /* B: 8-bit code, 8-bit XOR check, 0x7e0fa006[....] */
+        if (((bits ^ (~bits >> 8 & 255)) & 0xffff00ff) == 0xa0060000) {
+            /*          check:   [..pt gggg ~(..pt gggg)]
+             * 0xa006[....] -> 0b[.... ....   .... .... ] */
+            *code = bits >> 8 & 255;
+            return HDCD_CODE_B;
+        } else
+            return HDCD_CODE_B_CHECKFAIL;  /* XOR check failed */
+    }
+    if (bits == 0x7e0fa005)
+        return HDCD_CODE_EXPECT_A;
+    else if (bits == 0x7e0fa006)
+        return HDCD_CODE_EXPECT_B;
+
+    return HDCD_CODE_NONE;
+}
+
+static int hdcd_integrate(HDCDContext *ctx, hdcd_state_t *state, int *flag, const int32_t *samples, int count, int stride)
 {
     uint32_t bits = 0;
     int result = FFMIN(state->readahead, count);
@@ -918,44 +1001,39 @@ static int hdcd_integrate(hdcd_state_t *state, int *flag, const int32_t *samples
     bits = (state->window ^ state->window >> 5 ^ state->window >> 23);
 
     if (state->arg) {
-        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);
+        switch (hdcd_code(bits, &state->control)) {
+            case HDCD_CODE_A:
                 *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;
+                break;
+            case HDCD_CODE_B:
                 *flag = 1;
                 state->code_counterB++;
-            } else {
-                /* XOR check failed */
+                break;
+            case HDCD_CODE_A_ALMOST:
+                state->code_counterA_almost++;
+                av_log(ctx->fctx, AV_LOG_VERBOSE,
+                    "hdcd error: Control A almost: 0x%02x near %d\n", bits & 0xff, ctx->sample_count);
+                break;
+            case HDCD_CODE_B_CHECKFAIL:
                 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);
+                av_log(ctx->fctx, AV_LOG_VERBOSE,
+                    "hdcd error: Control B check failed: 0x%04x (0x%02x vs 0x%02x) near %d\n", bits & 0xffff, (bits & 0xff00) >> 8, ~bits & 0xff, ctx->sample_count);
+                break;
+            case HDCD_CODE_NONE:
+                state->code_counterC_unmatched++;
+                av_log(ctx->fctx, AV_LOG_VERBOSE,
+                    "hdcd error: Unmatched code: 0x%08x near %d\n", bits, ctx->sample_count);
+            default:
+                av_log(ctx->fctx, AV_LOG_INFO,
+                    "hdcd error: Unexpected return value from hdcd_code()\n");
+                av_assert0(0); /* die */
         }
         if (*flag) hdcd_update_info(state);
         state->arg = 0;
     }
     if (bits == 0x7e0fa005 || bits == 0x7e0fa006) {
+        /* 0x7e0fa00[.]-> [0b0101 or 0b0110] */
         state->readahead = (bits & 3) * 8;
         state->arg = 1;
         state->code_counterC++;
@@ -968,27 +1046,156 @@ static int hdcd_integrate(hdcd_state_t *state, int *flag, const int32_t *samples
     return result;
 }
 
-static int hdcd_scan(hdcd_state_t *state, const int32_t *samples, int max, int stride)
+static int hdcd_integrate_stereo(HDCDContext *ctx, int *flag, const int32_t *samples, int count)
+{
+    uint32_t bits[2] = {0, 0};
+    int result;
+    int i;
+    *flag = 0;
+
+    /* result = min(count, s0ra, s1ra) */
+    result = FFMIN(ctx->state[0].readahead, count);
+    result = FFMIN(ctx->state[1].readahead, result);
+
+    for (i = result - 1; i >= 0; i--) {
+        bits[0] |= (*(samples++) & 1) << i;
+        bits[1] |= (*(samples++) & 1) << i;
+    }
+
+    for (i = 0; i < 2; i++) {
+        ctx->state[i].window = (ctx->state[i].window << result) | bits[i];
+        ctx->state[i].readahead -= result;
+
+        if (ctx->state[i].readahead == 0) {
+            uint32_t wbits = (ctx->state[i].window ^ ctx->state[i].window >> 5 ^ ctx->state[i].window >> 23);
+            if (ctx->state[i].arg) {
+                switch (hdcd_code(wbits, &ctx->state[i].control)) {
+                    case HDCD_CODE_A:
+                        *flag |= i+1;
+                        ctx->state[i].code_counterA++;
+                        break;
+                    case HDCD_CODE_B:
+                        *flag |= i+1;
+                        ctx->state[i].code_counterB++;
+                        break;
+                    case HDCD_CODE_A_ALMOST:
+                        ctx->state[i].code_counterA_almost++;
+                        av_log(ctx->fctx, AV_LOG_VERBOSE,
+                            "hdcd error: Control A almost: 0x%02x near %d\n", wbits & 0xff, ctx->sample_count);
+                        break;
+                    case HDCD_CODE_B_CHECKFAIL:
+                        ctx->state[i].code_counterB_checkfails++;
+                        av_log(ctx->fctx, AV_LOG_VERBOSE,
+                            "hdcd error: Control B check failed: 0x%04x (0x%02x vs 0x%02x) near %d\n", wbits & 0xffff, (wbits & 0xff00) >> 8, ~wbits & 0xff, ctx->sample_count);
+                        break;
+                    case HDCD_CODE_NONE:
+                        ctx->state[i].code_counterC_unmatched++;
+                        av_log(ctx->fctx, AV_LOG_VERBOSE,
+                            "hdcd error: Unmatched code: 0x%08x near %d\n", wbits, ctx->sample_count);
+                    default:
+                        av_log(ctx->fctx, AV_LOG_INFO,
+                            "hdcd error: Unexpected return value from hdcd_code()\n");
+                        av_assert0(0); /* die */
+                }
+                if (*flag&(i+1)) hdcd_update_info(&ctx->state[i]);
+                ctx->state[i].arg = 0;
+            }
+            if (wbits == 0x7e0fa005 || wbits == 0x7e0fa006) {
+                /* 0x7e0fa00[.]-> [0b0101 or 0b0110] */
+                ctx->state[i].readahead = (wbits & 3) * 8;
+                ctx->state[i].arg = 1;
+                ctx->state[i].code_counterC++;
+            } else {
+                if (wbits)
+                    ctx->state[i].readahead = readaheadtab[wbits & 0xff];
+                else
+                    ctx->state[i].readahead = 31; /* ffwd over digisilence */
+            }
+        }
+    }
+    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(HDCDContext *ctx, hdcd_state_t *state, const int32_t *samples, int max, int stride)
 {
     int result;
+    int cdt_active = 0;
+    /* code detect timer */
     if (state->sustain > 0) {
+        cdt_active = 1;
         if (state->sustain <= max) {
             state->control = 0;
             max = state->sustain;
         }
         state->sustain -= max;
     }
+
     result = 0;
     while (result < max) {
         int flag;
-        int consumed = hdcd_integrate(state, &flag, samples, max - result, stride);
+        int consumed = hdcd_integrate(ctx, 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;
+}
+
+static int hdcd_scan_stereo(HDCDContext *ctx, const int32_t *samples, int max)
+{
+    int result;
+    int i;
+    int cdt_active[2] = {0, 0};
+
+    /* code detect timers for each channel */
+    for(i=0; i<2; i++) {
+        if (ctx->state[i].sustain > 0) {
+            cdt_active[i] = 1;
+            if (ctx->state[i].sustain <= max) {
+                ctx->state[i].control = 0;
+                max = ctx->state[i].sustain;
+            }
+            ctx->state[i].sustain -= max;
+        }
+    }
+
+    result = 0;
+    while (result < max) {
+        int flag;
+        int consumed = hdcd_integrate_stereo(ctx, &flag, samples, max - result);
+        result += consumed;
+        if (flag) {
+            /* reset timer if code detected in a channel */
+            if (flag & 1) hdcd_sustain_reset(&ctx->state[0]);
+            if (flag & 2) hdcd_sustain_reset(&ctx->state[1]);
+            break;
+        }
+        samples += consumed * 2;
+    }
+
+    for(i=0; i<2; i++) {
+        /* code detect timer expired */
+        if (cdt_active[i] && ctx->state[i].sustain == 0)
+            ctx->state[i].count_sustain_expired++;
+    }
+
     return result;
 }
 
@@ -1051,20 +1258,28 @@ static int hdcd_envelope(int32_t *samples, int count, int stride, int gain, int
     return gain;
 }
 
-static void hdcd_process(hdcd_state_t *state, int32_t *samples, int count, int stride)
+/* extract fields from control code */
+static void hdcd_control(hdcd_state_t *state, int *peak_extend, int *target_gain)
+{
+    *peak_extend = !!(state->control & 16);
+    *target_gain = (state->control & 15) << 7;
+}
+
+static void hdcd_process(HDCDContext *ctx, hdcd_state_t *state, int32_t *samples, int count, int stride)
 {
     int32_t *samples_end = samples + count * stride;
     int gain = state->running_gain;
-    int peak_extend = (state->control & 16);
-    int target_gain = (state->control & 15) << 7;
+    int peak_extend, target_gain;
     int lead = 0;
 
+    hdcd_control(state, &peak_extend, &target_gain);
+    if (ctx->force_pe) peak_extend = 1;
     while (count > lead) {
         int envelope_run;
         int run;
 
         av_assert0(samples + lead * stride + stride * (count - lead) <= samples_end);
-        run = hdcd_scan(state, samples + lead * stride, count - lead, stride) + lead;
+        run = hdcd_scan(ctx, state, samples + lead * stride, count - lead, stride) + lead;
         envelope_run = run - 1;
 
         av_assert0(samples + envelope_run * stride <= samples_end);
@@ -1073,8 +1288,8 @@ static void hdcd_process(hdcd_state_t *state, int32_t *samples, int count, int s
         samples += envelope_run * stride;
         count -= envelope_run;
         lead = run - envelope_run;
-        peak_extend = (state->control & 16);
-        target_gain = (state->control & 15) << 7;
+        hdcd_control(state, &peak_extend, &target_gain);
+        if (ctx->force_pe) peak_extend = 1;
     }
     if (lead > 0) {
         av_assert0(samples + lead * stride <= samples_end);
@@ -1084,6 +1299,51 @@ static void hdcd_process(hdcd_state_t *state, int32_t *samples, int count, int s
     state->running_gain = gain;
 }
 
+static void hdcd_process_stereo(HDCDContext *ctx, int32_t *samples, int count)
+{
+    const int stride = 2;
+    int32_t *samples_end = samples + count * stride;
+    int gain[2] = {ctx->state[0].running_gain, ctx->state[1].running_gain};
+    int peak_extend[2], target_gain[2];
+    int lead = 0;
+
+    hdcd_control(&ctx->state[0], &peak_extend[0], &target_gain[0]);
+    hdcd_control(&ctx->state[1], &peak_extend[1], &target_gain[1]);
+    if (target_gain[0] == target_gain[1]) ctx->val_target_gain = target_gain[0];
+    if (ctx->force_pe) { peak_extend[0] = 1; peak_extend[1] = 1; }
+    while (count > lead) {
+        int envelope_run, run;
+
+        av_assert0(samples + lead * stride + stride * (count - lead) <= samples_end);
+        run = hdcd_scan_stereo(ctx, samples + lead * stride, count - lead) + lead;
+        envelope_run = run - 1;
+
+        av_assert0(samples + envelope_run * stride <= samples_end);
+
+        if (envelope_run) {
+            gain[0] = hdcd_envelope(samples, envelope_run, stride, gain[0], ctx->val_target_gain, peak_extend[0]);
+            gain[1] = hdcd_envelope(samples + 1, envelope_run, stride, gain[1], ctx->val_target_gain, peak_extend[1]);
+        }
+
+        samples += envelope_run * stride;
+        count -= envelope_run;
+        lead = run - envelope_run;
+
+        hdcd_control(&ctx->state[0], &peak_extend[0], &target_gain[0]);
+        hdcd_control(&ctx->state[1], &peak_extend[1], &target_gain[1]);
+        if (target_gain[0] == target_gain[1]) ctx->val_target_gain = target_gain[0];
+        if (ctx->force_pe) { peak_extend[0] = 1; peak_extend[1] = 1; }
+    }
+    if (lead > 0) {
+        av_assert0(samples + lead * stride <= samples_end);
+        gain[0] = hdcd_envelope(samples, lead, stride, gain[0], ctx->val_target_gain, peak_extend[0]);
+        gain[1] = hdcd_envelope(samples + 1, lead, stride, gain[1], ctx->val_target_gain, peak_extend[1]);
+    }
+
+    ctx->state[0].running_gain = gain[0];
+    ctx->state[1].running_gain = gain[1];
+}
+
 /* convert to float from 4-bit (3.1) fixed-point
  * the always-negative value is stored positive, so make it negative */
 #define GAINTOFLOAT(g) (g) ? -(float)(g>>1) - ((g & 1) ? 0.5 : 0.0) : 0.0
@@ -1112,19 +1372,61 @@ static int filter_frame(AVFilterLink *inlink, AVFrame *in)
         out_data[n] = in_data[n];
     }
 
-    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;
-        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_counterB || state->code_counterA;
-        s->det_errors += state->code_counterA_almost
-            + state->code_counterB_checkfails
-            + state->code_counterC_unmatched;
+    s->det_errors = 0; /* re-sum every pass */
+    if (s->process_stereo && inlink->channels == 2) {
+        float mga = 0.0;
+        hdcd_process_stereo(s, out_data, in->nb_samples);
+        /* HDCD is detected if a valid packet is active in both
+         * channels at the same time. */
+        if (s->state[0].sustain && s->state[1].sustain) s->hdcd_detected = 1;
+        if (s->state[0].count_peak_extend || s->state[1].count_peak_extend) {
+            int packets = s->state[0].code_counterA
+                + s->state[0].code_counterB
+                + s->state[1].code_counterA
+                + s->state[1].code_counterB;
+            /* if every valid packet has used PE, call it permanent */
+            if (packets == s->state[0].count_peak_extend + s->state[1].count_peak_extend)
+                s->peak_extend = HDCD_PE_PERMANENT;
+            else
+                s->peak_extend = HDCD_PE_INTERMITTENT;
+        } else s->peak_extend = HDCD_PE_NEVER;
+        s->uses_transient_filter = (s->state[0].count_transient_filter || s->state[1].count_transient_filter);
+        mga = FFMIN(GAINTOFLOAT(s->state[0].max_gain), GAINTOFLOAT(s->state[1].max_gain));
+        s->max_gain_adjustment = FFMIN(s->max_gain_adjustment, mga);
+        s->det_errors += s->state[0].code_counterA_almost
+            + s->state[0].code_counterB_checkfails
+            + s->state[0].code_counterC_unmatched
+            + s->state[1].code_counterA_almost
+            + s->state[1].code_counterB_checkfails
+            + s->state[1].code_counterC_unmatched;
+    } else {
+        int detect=0;
+        int packets=0;
+        int pe_packets=0;
+        for (c = 0; c < inlink->channels; c++) {
+            hdcd_state_t *state = &s->state[c];
+            hdcd_process(s, state, out_data + c, in->nb_samples, out->channels);
+            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->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
+         * channels at the same time. */
+        if (detect == inlink->channels) s->hdcd_detected = 1;
     }
+    s->sample_count += in->nb_samples * in->channels;
 
     av_frame_free(&in);
     return ff_filter_frame(outlink, out);
@@ -1182,16 +1484,17 @@ static av_cold void uninit(AVFilterContext *ctx)
     int i, j;
 
     /* dump the state for each channel for AV_LOG_VERBOSE */
-    for (i = 0; i < 2; i++) {
+    for (i = 0; i < HDCD_MAX_CHANNELS; 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: pe: %d, tf: %d, almost_A: %d, checkfail_B: %d, unmatched_C: %d\n", i,
+        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->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]);
         }
@@ -1201,7 +1504,7 @@ static av_cold void uninit(AVFilterContext *ctx)
     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",
-            (s->uses_peak_extend) ? "enabled" : "never enabled",
+            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)" : ""
@@ -1217,12 +1520,18 @@ static av_cold int init(AVFilterContext *ctx)
     int c;
 
     s->max_gain_adjustment = 0.0;
+    s->sample_count = 0;
+    s->fctx = ctx;
 
-    for (c = 0; c < 2; c++) {
+    for (c = 0; c < HDCD_MAX_CHANNELS; c++) {
         hdcd_reset(&s->state[c], 44100);
-        s->state[c].fctx = ctx;
     }
 
+    av_log(ctx, AV_LOG_VERBOSE, "Process mode: %s\n",
+        (s->process_stereo) ? "process stereo channels together" : "process each channel separately");
+    av_log(ctx, AV_LOG_VERBOSE, "Force PE: %s\n",
+        (s->force_pe) ? "on" : "off");
+
     return 0;
 }
 
-- 
2.7.4



More information about the ffmpeg-devel mailing list