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:
parent
3e4357eb82
commit
ba69a81019
@ -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
|
||||||
|
|
||||||
|
@ -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,
|
||||||
|
Loading…
Reference in New Issue
Block a user