diff options
-rw-r--r-- | doc/filters.texi | 19 | ||||
-rw-r--r-- | libavfilter/af_hdcd.c | 76 |
2 files changed, 90 insertions, 5 deletions
diff --git a/doc/filters.texi b/doc/filters.texi index 3cf3d7ce89..a8c2c8799d 100644 --- a/doc/filters.texi +++ b/doc/filters.texi @@ -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 diff --git a/libavfilter/af_hdcd.c b/libavfilter/af_hdcd.c index 16bdcb01ea..89012eb53e 100644 --- a/libavfilter/af_hdcd.c +++ b/libavfilter/af_hdcd.c @@ -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, |