libavfilter/af_hdcd.c: Collect HDCD stats and report
authorBurt P <pburt0@gmail.com>
Tue, 5 Jul 2016 17:23:33 +0000 (12:23 -0500)
committerMichael Niedermayer <michael@niedermayer.cc>
Wed, 6 Jul 2016 20:54:47 +0000 (22:54 +0200)
The new HDCD filter really does nothing to show that it is working or
that HDCD control information was even detected in the stream. This
patch collects information about the decode, like which features were
used, and reports it to the user at the end.

Also,
* Fixes low-level gain adjustment
* Updates the documentation

Signed-off-by: Burt P <pburt0@gmail.com>
Reviewed-by: Paul B Mahol <onemda@gmail.com>
Signed-off-by: Michael Niedermayer <michael@niedermayer.cc>
doc/filters.texi
libavfilter/af_hdcd.c

index 3cf3d7c..a8c2c87 100644 (file)
@@ -8427,8 +8427,23 @@ ffplay input.mkv -vf "movie=clut.png, [in] haldclut"
 
 @section hdcd
 
-Decodes high definition audio cd data. 16-Bit PCM stream containing hdcd flags
-is converted to 20-bit PCM stream.
+Decodes High Definition Compatible Digital (HDCD) data. A 16-bit PCM stream with
+embedded HDCD codes is expanded into a 20-bit PCM stream.
+
+The filter supports the Peak Extend and Low-level Gain Adjustment features
+of HDCD, and detects the Transient Filter flag.
+
+@example
+ffmpeg -i HDCD16.flac -af hdcd OUT24.flac
+@end example
+
+When using the filter with wav, note the default encoding for wav is 16-bit,
+so the resulting 20-bit stream will be truncated back to 16-bit. Use something
+like @command{-acodec pcm_s24le} after the filter to get 24-bit PCM output.
+@example
+ffmpeg -i HDCD16.wav -af hdcd OUT16.wav
+ffmpeg -i HDCD16.wav -af hdcd -acodec pcm_s24le OUT24.wav
+@end example
 
 @section hflip
 
index 16bdcb0..89012eb 100644 (file)
@@ -31,7 +31,8 @@
 */
 
 /*
-  More information about high definition audio cds:
+  HDCD is High Definition Compatible Digital
+  More information about HDCD-encoded audio CDs:
   http://www.audiomisc.co.uk/HFN/HDCD/Enigma.html
   http://www.audiomisc.co.uk/HFN/HDCD/Examined.html
  */
@@ -823,11 +824,29 @@ typedef struct {
     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;
+    /* 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 */
 } hdcd_state_t;
 
 typedef struct HDCDContext {
     const AVClass *class;
     hdcd_state_t state[2];
+
+    /* User information/stats */
+    int hdcd_detected;
+    int uses_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;
 
 static const AVOption hdcd_options[] = {
@@ -840,6 +859,8 @@ AVFILTER_DEFINE_CLASS(hdcd);
 
 static void hdcd_reset(hdcd_state_t *state, unsigned rate)
 {
+    int i;
+
     state->window = 0;
     state->readahead = 32;
     state->arg = 0;
@@ -853,6 +874,13 @@ static void hdcd_reset(hdcd_state_t *state, unsigned rate)
     state->code_counterA = 0;
     state->code_counterB = 0;
     state->code_counterC = 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;
 }
 
 static int integrate(hdcd_state_t *state, int *flag, const int32_t *samples, int count, int stride)
@@ -949,6 +977,7 @@ static int hdcd_envelope(int32_t *samples, int count, int stride, int gain, int
         int len = FFMIN(count, target_gain - gain);
         /* attenuate slowly */
         for (i = 0; i < len; i++) {
+            ++gain;
             APPLY_GAIN(*samples, gain);
             samples += stride;
         }
@@ -982,6 +1011,18 @@ 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;
@@ -990,6 +1031,8 @@ 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;
@@ -1006,6 +1049,7 @@ 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);
@@ -1015,6 +1059,10 @@ static void hdcd_process(hdcd_state_t *state, int32_t *samples, int count, int s
     state->running_gain = gain;
 }
 
+/* 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
+
 static int filter_frame(AVFilterLink *inlink, AVFrame *in)
 {
     AVFilterContext *ctx = inlink->dst;
@@ -1042,6 +1090,11 @@ static int filter_frame(AVFilterLink *inlink, AVFrame *in)
     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_counterC || state->code_counterB || state->code_counterA;
     }
 
     av_frame_free(&in);
@@ -1097,13 +1150,28 @@ static int query_formats(AVFilterContext *ctx)
 static av_cold void uninit(AVFilterContext *ctx)
 {
     HDCDContext *s = ctx->priv;
-    int i;
+    int i, j;
 
+    /* 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);
+        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]);
+        }
     }
+
+    /* 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"
+        );
 }
 
 static av_cold int init(AVFilterContext *ctx)
@@ -1112,6 +1180,8 @@ static av_cold int init(AVFilterContext *ctx)
     HDCDContext *s = ctx->priv;
     int c;
 
+    s->max_gain_adjustment = 0.0;
+
     for (c = 0; c < 2; c++) {
         hdcd_reset(&s->state[c], 44100);
     }
@@ -1138,7 +1208,7 @@ static const AVFilterPad avfilter_af_hdcd_outputs[] = {
 
 AVFilter ff_af_hdcd = {
     .name          = "hdcd",
-    .description   = NULL_IF_CONFIG_SMALL("Apply high definition audio cd decoding."),
+    .description   = NULL_IF_CONFIG_SMALL("Apply High Definition Compatible Digital (HDCD) decoding."),
     .priv_size     = sizeof(HDCDContext),
     .priv_class    = &hdcd_class,
     .init          = init,