af_hdcd: for easier maintenance alongside libhdcd
authorBurt P <pburt0@gmail.com>
Mon, 22 Aug 2016 22:34:35 +0000 (17:34 -0500)
committerBurt P <pburt0@gmail.com>
Thu, 25 Aug 2016 22:11:57 +0000 (17:11 -0500)
Mostly just re-arranges some code to make it easier to update this
filter and libhdcd together. filter_frame() is much simpler as a
result.

* use the HDCD detection data structure and functions from libhdcd,
  moved detection code out of filter_frame()
* moved analyze_mode preparation out of filter_frame() into
  hdcd_analyze_prepare(), from libhdcd
* moved some macro definitions to the top so they are all together

Signed-off-by: Burt P <pburt0@gmail.com>
libavfilter/af_hdcd.c

index 2324dc3..0360bc9 100644 (file)
@@ -823,6 +823,20 @@ static const int32_t gaintab[] = {
     0x35fa26
 };
 
+#define HDCD_PROCESS_STEREO_DEFAULT 1
+#define HDCD_MAX_CHANNELS 2
+
+/** 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
+
+/** apply gain, 11-bit (3.8) fixed point,
+ *  always negative but stored positive. */
+#define APPLY_GAIN(s,g) do{int64_t s64 = s; s64 *= gaintab[g]; s = (int32_t)(s64 >> 23); }while(0);
+
+/** tone generator: sample_number, frequency, sample_rate, amplitude */
+#define TONEGEN16(sn, f, sr, a) (int16_t)(sin((6.28318530718 * (sn) * (f)) /(sr)) * (a) * 0x7fff)
+
 typedef struct {
     uint64_t window;
     unsigned char readahead;
@@ -856,12 +870,14 @@ typedef struct {
     /** occurences of code detect timer expiring without detecting
      *  a code. -1 for timer never set. */
     int count_sustain_expired;
+
+    int _ana_snb;               /**< used in the analyze mode tone generator */
 } hdcd_state_t;
 
 typedef enum {
-    HDCD_PE_NEVER        = 0,
-    HDCD_PE_INTERMITTENT = 1,
-    HDCD_PE_PERMANENT    = 2,
+    HDCD_PE_NEVER        = 0, /**< All valid packets have PE set to off */
+    HDCD_PE_INTERMITTENT = 1, /**< Some valid packets have PE set to on */
+    HDCD_PE_PERMANENT    = 2, /**< All valid packets have PE set to on  */
 } hdcd_pe_t;
 
 static const char * const pe_str[] = {
@@ -870,25 +886,51 @@ static const char * const pe_str[] = {
     "enabled permanently"
 };
 
-#define HDCD_PROCESS_STEREO_DEFAULT 1
-#define HDCD_MAX_CHANNELS 2
+typedef enum {
+    HDCD_NONE            = 0,  /**< HDCD packets do not (yet) appear  */
+    HDCD_NO_EFFECT       = 1,  /**< HDCD packets appear, but all control codes are NOP */
+    HDCD_EFFECTUAL       = 2,  /**< HDCD packets appear, and change the output in some way */
+} hdcd_detection_t;
 
-/** 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
+typedef enum {
+    HDCD_PVER_NONE       = 0, /**< No packets (yet) discovered */
+    HDCD_PVER_A          = 1, /**< Packets of type A (8-bit control) discovered */
+    HDCD_PVER_B          = 2, /**< Packets of type B (8-bit control, 8-bit XOR) discovered */
+    HDCD_PVER_MIX        = 3, /**< Packets of type A and B discovered, most likely an encoding error */
+} hdcd_pf_t;
+
+static const char * const pf_str[] = {
+    "?", "A", "B", "A+B"
+};
+
+typedef struct {
+    hdcd_detection_t hdcd_detected;
+    hdcd_pf_t packet_type;
+    int total_packets;         /**< valid packets */
+    int errors;                /**< detectable errors */
+    hdcd_pe_t peak_extend;
+    int uses_transient_filter;
+    float max_gain_adjustment; /**< in dB, expected in the range -7.5 to 0.0 */
+    int cdt_expirations;       /**< -1 for never set, 0 for set but never expired */
 
-#define HDCD_ANA_OFF 0
+    int _active_count;         /**< used internally */
+} hdcd_detection_data_t;
+
+typedef enum {
+    HDCD_ANA_OFF    = 0,
+    HDCD_ANA_LLE    = 1,
+    HDCD_ANA_PE     = 2,
+    HDCD_ANA_CDT    = 3,
+    HDCD_ANA_TGM    = 4,
+    HDCD_ANA_TOP    = 5, /**< used in max value of AVOption */
+} hdcd_ana_mode_t;
+
+/** analyze mode descriptions: macro for AVOption definitions, array of const char for mapping mode to string */
 #define HDCD_ANA_OFF_DESC "disabled"
-#define HDCD_ANA_LLE 1
 #define HDCD_ANA_LLE_DESC "gain adjustment level at each sample"
-#define HDCD_ANA_PE  2
 #define HDCD_ANA_PE_DESC  "samples where peak extend occurs"
-#define HDCD_ANA_CDT 3
 #define HDCD_ANA_CDT_DESC "samples where the code detect timer is active"
-#define HDCD_ANA_TGM 4
 #define HDCD_ANA_TGM_DESC "samples where the target gain does not match between channels"
-#define HDCD_ANA_TOP 5 /* used in max value of AVOption */
-
 static const char * const ana_mode_str[] = {
     HDCD_ANA_OFF_DESC,
     HDCD_ANA_LLE_DESC,
@@ -917,7 +959,6 @@ typedef struct HDCDContext {
      *  the amplitude to signal some specific aspect of the decoding
      *  process. See docs or HDCD_ANA_* defines. */
     int analyze_mode;
-    int ana_snb;              /**< used in tone generation */
 
     int cdt_ms;               /**< code detect timer period in ms */
 
@@ -934,11 +975,7 @@ typedef struct HDCDContext {
     int val_target_gain;   /**< last matching target_gain in both channels */
 
     /* User information/stats */
-    int hdcd_detected;         /**< Valid HDCD coding was detected */
-    int det_errors;            /**< detectable errors */
-    hdcd_pe_t peak_extend;     /**< peak exted used */
-    int uses_transient_filter; /**< transient filter flag detected */
-    float max_gain_adjustment; /**< in dB, expected in the range -7.5 to 0.0 */
+    hdcd_detection_data_t detect;
 } HDCDContext;
 
 #define OFFSET(x) offsetof(HDCDContext, x)
@@ -964,8 +1001,6 @@ static const AVOption hdcd_options[] = {
 
 AVFILTER_DEFINE_CLASS(hdcd);
 
-#define APPLY_GAIN(s,g) do{int64_t s64 = s; s64 *= gaintab[g]; s = (int32_t)(s64 >> 23); }while(0);
-
 static void hdcd_reset(hdcd_state_t *state, unsigned rate, unsigned cdt_ms)
 {
     int i;
@@ -991,6 +1026,8 @@ static void hdcd_reset(hdcd_state_t *state, unsigned rate, unsigned cdt_ms)
     for(i = 0; i < 16; i++) state->gain_counts[i] = 0;
     state->max_gain = 0;
     state->count_sustain_expired = -1;
+
+    state->_ana_snb = 0;
 }
 
 /** update the user info/counters */
@@ -1259,6 +1296,23 @@ static int hdcd_scan_stereo(HDCDContext *ctx, const int32_t *samples, int max)
     return result;
 }
 
+/** replace audio with solid tone, but save LSBs */
+static void hdcd_analyze_prepare(hdcd_state_t *state, int32_t *samples, int count, int stride) {
+    int n;
+    for (n = 0; n < count * stride; n += stride) {
+        /* in analyze mode, the audio is replaced by a solid tone, and
+         * amplitude is changed to signal when the specified feature is
+         * used.
+         * bit 0: HDCD signal preserved
+         * bit 1: Original sample was above PE level */
+        int32_t save = (abs(samples[n]) - PEAK_EXT_LEVEL >= 0) ? 2 : 0; /* above PE level */
+        save |= samples[n] & 1;                      /* save LSB for HDCD packets */
+        samples[n] = TONEGEN16(state->_ana_snb, 277.18, 44100, 0.1);
+        samples[n] = (samples[n] | 3) ^ ((~save) & 3);
+        if (++state->_ana_snb > 0x3fffffff) state->_ana_snb = 0;
+    }
+}
+
 /** encode a value in the given sample by adjusting the amplitude */
 static int32_t hdcd_analyze_gen(int32_t sample, unsigned int v, unsigned int maxv)
 {
@@ -1429,6 +1483,9 @@ static void hdcd_process(HDCDContext *ctx, hdcd_state_t *state, int32_t *samples
     int peak_extend, target_gain;
     int lead = 0;
 
+    if (ctx->analyze_mode)
+        hdcd_analyze_prepare(state, samples, count, stride);
+
     hdcd_control(ctx, state, &peak_extend, &target_gain);
     while (count > lead) {
         int envelope_run;
@@ -1467,8 +1524,14 @@ static void hdcd_process_stereo(HDCDContext *ctx, int32_t *samples, int count)
     int gain[2] = {ctx->state[0].running_gain, ctx->state[1].running_gain};
     int peak_extend[2];
     int lead = 0;
+    int ctlret;
+
+    if (ctx->analyze_mode) {
+        hdcd_analyze_prepare(&ctx->state[0], samples, count, stride);
+        hdcd_analyze_prepare(&ctx->state[1], samples + 1, count, stride);
+    }
 
-    int ctlret = hdcd_control_stereo(ctx, &peak_extend[0], &peak_extend[1]);
+    ctlret = hdcd_control_stereo(ctx, &peak_extend[0], &peak_extend[1]);
     while (count > lead) {
         int envelope_run, run;
 
@@ -1519,8 +1582,61 @@ static void hdcd_process_stereo(HDCDContext *ctx, int32_t *samples, int count)
     ctx->state[1].running_gain = gain[1];
 }
 
-/** tone generator: sample_number, frequency, sample_rate, amplitude */
-#define TONEGEN16(sn, f, sr, a) (int16_t)(sin((6.28318530718 * (sn) * (f)) /(sr)) * (a) * 0x7fff)
+static void hdcd_detect_reset(hdcd_detection_data_t *detect) {
+    detect->hdcd_detected = HDCD_NONE;
+    detect->packet_type = HDCD_PVER_NONE;
+    detect->total_packets = 0;
+    detect->errors = 0;
+    detect->peak_extend = HDCD_PE_NEVER;
+    detect->uses_transient_filter = 0;
+    detect->max_gain_adjustment = 0.0;
+    detect->cdt_expirations = -1;
+    detect->_active_count = 0;
+}
+
+static void hdcd_detect_start(hdcd_detection_data_t *detect) {
+    detect->errors = 0;          /* re-sum every pass */
+    detect->total_packets = 0;
+    detect->_active_count = 0;   /* will need to match channels at hdcd_detect_end() */
+    detect->cdt_expirations = -1;
+}
+
+static void hdcd_detect_onech(hdcd_state_t *state, hdcd_detection_data_t *detect) {
+    hdcd_pe_t pe = HDCD_PE_NEVER;
+    detect->uses_transient_filter |= !!(state->count_transient_filter);
+    detect->total_packets += state->code_counterA + state->code_counterB;
+    if (state->code_counterA) detect->packet_type |= HDCD_PVER_A;
+    if (state->code_counterB) detect->packet_type |= HDCD_PVER_B;
+    if (state->count_peak_extend) {
+        /* if every valid packet has used PE, call it permanent */
+        if (state->count_peak_extend == state->code_counterA + state->code_counterB)
+            pe = HDCD_PE_PERMANENT;
+        else
+            pe = HDCD_PE_INTERMITTENT;
+        if (detect->peak_extend != HDCD_PE_INTERMITTENT)
+            detect->peak_extend = pe;
+    }
+    detect->max_gain_adjustment = FFMIN(detect->max_gain_adjustment, GAINTOFLOAT(state->max_gain));
+    detect->errors += state->code_counterA_almost
+        + state->code_counterB_checkfails
+        + state->code_counterC_unmatched;
+    if (state->sustain) detect->_active_count++;
+    if (state->count_sustain_expired >= 0) {
+        if (detect->cdt_expirations == -1) detect->cdt_expirations = 0;
+        detect->cdt_expirations += state->count_sustain_expired;
+    }
+}
+
+static void hdcd_detect_end(hdcd_detection_data_t *detect, int channels) {
+    /* HDCD is detected if a valid packet is active in all
+     * channels at the same time. */
+    if (detect->_active_count == channels) {
+        if (detect->max_gain_adjustment || detect->peak_extend)
+            detect->hdcd_detected = HDCD_EFFECTUAL;
+        else
+            detect->hdcd_detected = HDCD_NO_EFFECT;
+    }
+}
 
 static int filter_frame(AVFilterLink *inlink, AVFrame *in)
 {
@@ -1542,81 +1658,28 @@ static int filter_frame(AVFilterLink *inlink, AVFrame *in)
         av_frame_free(&in);
         return result;
     }
-    out->format = outlink->format;
+    out->format = outlink->format; // is this needed?
 
     in_data  = (int16_t*)in->data[0];
     out_data = (int32_t*)out->data[0];
-    for (c = n = 0; n < in->nb_samples * in->channels; n++) {
+    for (c = n = 0; n < in->nb_samples * in->channels; n++)
         out_data[n] = in_data[n];
-        if (s->analyze_mode) {
-            /* in analyze mode, the audio is replaced by a solid tone, and
-             * amplitude is changed to signal when the specified feature is
-             * used.
-             * bit 0: HDCD signal preserved
-             * bit 1: Original sample was above PE level */
-            int32_t save = (abs(in_data[n]) - PEAK_EXT_LEVEL >= 0) ? 2 : 0; /* above PE level */
-            save |= in_data[n] & 1;                      /* save LSB for HDCD packets */
-            out_data[n] = TONEGEN16(s->ana_snb, 277.18, 44100, 0.1);
-            out_data[n] = (out_data[n] | 3) ^ ((~save) & 3);
-            if (++c == in->channels) { s->ana_snb++; c = 0; }
-            if (s->ana_snb > 0x3fffffff) s->ana_snb = 0;
-        }
-    }
 
-    s->det_errors = 0; /* re-sum every pass */
-    if (s->process_stereo && inlink->channels == 2) {
-        float mga = 0.0;
+    if (s->process_stereo) {
+        hdcd_detect_start(&s->detect);
         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;
+        hdcd_detect_onech(&s->state[0], &s->detect);
+        hdcd_detect_onech(&s->state[1], &s->detect);
+        hdcd_detect_end(&s->detect, 2);
     } 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;
+        hdcd_detect_start(&s->detect);
+        for (c = 0; c < in->channels; c++) {
+            hdcd_process(s, &s->state[c], out_data + c, in->nb_samples, in->channels);
+            hdcd_detect_onech(&s->state[c], &s->detect);
         }
-        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;
+        hdcd_detect_end(&s->detect, in->channels);
     }
+
     s->sample_count += in->nb_samples * in->channels;
 
     av_frame_free(&in);
@@ -1690,15 +1753,18 @@ static av_cold void uninit(AVFilterContext *ctx)
             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, "Packets: type: %s, total: %d\n",
+        pf_str[s->detect.packet_type],
+        s->detect.total_packets);
 
     /* log the HDCD decode information */
-    if (s->hdcd_detected)
+    if (s->detect.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%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)" : "",
+            pe_str[s->detect.peak_extend],
+            s->detect.max_gain_adjustment,
+            (s->detect.uses_transient_filter) ? "detected" : "not detected",
+            s->detect.errors, (s->detect.errors) ? " (try -v verbose)" : "",
             (s->bad_config) ? " (bad_config)" : ""
             );
     else
@@ -1713,11 +1779,11 @@ static av_cold int init(AVFilterContext *ctx)
     HDCDContext *s = ctx->priv;
     int c;
 
-    s->max_gain_adjustment = 0.0;
     s->sample_count = 0;
     s->fctx = ctx;
     s->bad_config = 0;
 
+    hdcd_detect_reset(&s->detect);
     for (c = 0; c < HDCD_MAX_CHANNELS; c++) {
         hdcd_reset(&s->state[c], 44100, s->cdt_ms);
     }
@@ -1742,7 +1808,14 @@ static av_cold int init(AVFilterContext *ctx)
 static int config_input(AVFilterLink *inlink) {
     AVFilterContext *ctx = inlink->dst;
     HDCDContext *s = ctx->priv;
-    AVFilterLink *lk = inlink;
+    AVFilterLink *lk;
+
+    if (inlink->channels != 2 && s->process_stereo) {
+        av_log(ctx, AV_LOG_WARNING, "process_stereo disabled (channels = %d)", inlink->channels);
+        s->process_stereo = 0;
+    }
+
+    lk = inlink;
     while(lk != NULL) {
         AVFilterContext *nextf = lk->src;
         if (lk->type == AVMEDIA_TYPE_AUDIO) {