libavfilter/af_hdcd.c: Collect HDCD stats and report

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>
This commit is contained in:
Burt P 2016-07-05 12:23:33 -05:00 committed by Michael Niedermayer
parent 3e4357eb82
commit ba69a81019
2 changed files with 90 additions and 5 deletions

View File

@ -8427,8 +8427,23 @@ ffplay input.mkv -vf "movie=clut.png, [in] haldclut"
@section hdcd @section hdcd
Decodes high definition audio cd data. 16-Bit PCM stream containing hdcd flags Decodes High Definition Compatible Digital (HDCD) data. A 16-bit PCM stream with
is converted to 20-bit PCM stream. 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 @section hflip

View 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/Enigma.html
http://www.audiomisc.co.uk/HFN/HDCD/Examined.html http://www.audiomisc.co.uk/HFN/HDCD/Examined.html
*/ */
@ -823,11 +824,29 @@ typedef struct {
int code_counterA; int code_counterA;
int code_counterB; int code_counterB;
int code_counterC; 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; } hdcd_state_t;
typedef struct HDCDContext { typedef struct HDCDContext {
const AVClass *class; const AVClass *class;
hdcd_state_t state[2]; 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; } HDCDContext;
static const AVOption hdcd_options[] = { static const AVOption hdcd_options[] = {
@ -840,6 +859,8 @@ AVFILTER_DEFINE_CLASS(hdcd);
static void hdcd_reset(hdcd_state_t *state, unsigned rate) static void hdcd_reset(hdcd_state_t *state, unsigned rate)
{ {
int i;
state->window = 0; state->window = 0;
state->readahead = 32; state->readahead = 32;
state->arg = 0; state->arg = 0;
@ -853,6 +874,13 @@ static void hdcd_reset(hdcd_state_t *state, unsigned rate)
state->code_counterA = 0; state->code_counterA = 0;
state->code_counterB = 0; state->code_counterB = 0;
state->code_counterC = 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) 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); int len = FFMIN(count, target_gain - gain);
/* attenuate slowly */ /* attenuate slowly */
for (i = 0; i < len; i++) { for (i = 0; i < len; i++) {
++gain;
APPLY_GAIN(*samples, gain); APPLY_GAIN(*samples, gain);
samples += stride; samples += stride;
} }
@ -982,6 +1011,18 @@ static int hdcd_envelope(int32_t *samples, int count, int stride, int gain, int
return gain; 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) static void hdcd_process(hdcd_state_t *state, int32_t *samples, int count, int stride)
{ {
int32_t *samples_end = samples + count * 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 target_gain = (state->control & 15) << 7;
int lead = 0; int lead = 0;
hdcd_update_info(state);
while (count > lead) { while (count > lead) {
int envelope_run; int envelope_run;
int 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; lead = run - envelope_run;
peak_extend = (state->control & 16); peak_extend = (state->control & 16);
target_gain = (state->control & 15) << 7; target_gain = (state->control & 15) << 7;
hdcd_update_info(state);
} }
if (lead > 0) { if (lead > 0) {
av_assert0(samples + lead * stride <= samples_end); 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; 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) static int filter_frame(AVFilterLink *inlink, AVFrame *in)
{ {
AVFilterContext *ctx = inlink->dst; AVFilterContext *ctx = inlink->dst;
@ -1042,6 +1090,11 @@ static int filter_frame(AVFilterLink *inlink, AVFrame *in)
for (c = 0; c < inlink->channels; c++) { for (c = 0; c < inlink->channels; c++) {
hdcd_state_t *state = &s->state[c]; hdcd_state_t *state = &s->state[c];
hdcd_process(state, out_data + c, in->nb_samples, out->channels); 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); av_frame_free(&in);
@ -1097,13 +1150,28 @@ static int query_formats(AVFilterContext *ctx)
static av_cold void uninit(AVFilterContext *ctx) static av_cold void uninit(AVFilterContext *ctx)
{ {
HDCDContext *s = ctx->priv; 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++) { for (i = 0; i < 2; i++) {
hdcd_state_t *state = &s->state[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, 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); 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) static av_cold int init(AVFilterContext *ctx)
@ -1112,6 +1180,8 @@ static av_cold int init(AVFilterContext *ctx)
HDCDContext *s = ctx->priv; HDCDContext *s = ctx->priv;
int c; int c;
s->max_gain_adjustment = 0.0;
for (c = 0; c < 2; c++) { for (c = 0; c < 2; c++) {
hdcd_reset(&s->state[c], 44100); hdcd_reset(&s->state[c], 44100);
} }
@ -1138,7 +1208,7 @@ static const AVFilterPad avfilter_af_hdcd_outputs[] = {
AVFilter ff_af_hdcd = { AVFilter ff_af_hdcd = {
.name = "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_size = sizeof(HDCDContext),
.priv_class = &hdcd_class, .priv_class = &hdcd_class,
.init = init, .init = init,