diff options
-rw-r--r-- | doc/filters.texi | 32 | ||||
-rw-r--r-- | libavfilter/af_hdcd.c | 163 |
2 files changed, 187 insertions, 8 deletions
diff --git a/doc/filters.texi b/doc/filters.texi index 969df5e..bf95e0f 100644 --- a/doc/filters.texi +++ b/doc/filters.texi @@ -8356,6 +8356,38 @@ ffmpeg -i HDCD16.wav -af hdcd OUT16.wav ffmpeg -i HDCD16.wav -af hdcd -acodec pcm_s24le OUT24.wav @end example +The filter accepts the following options: + +@table @option +@item process_stereo +Process the stereo channels together. If target_gain does not match between +channels, consider it invalid and use the last valid target_gain. + +@item force_pe +Always extend peaks above -3dBFS even if PE isn't signaled. + +@item analyze_mode +Replace audio with a solid tone and adjust the amplitude to signal some +specific aspect of the decoding process. The output file can be loaded in +an audio editor alongside the original to aid analysis. + +@code{analyze_mode=pe:force_pe=1} can be used to see all samples above the PE level. + +Modes are: +@table @samp +@item 0, off +Disabled +@item 1, lle +Gain adjustment level at each sample +@item 2, pe +Samples where peak extend occurs +@item 3, cdt +Samples where the code detect timer is active +@item 4, tgm +Samples where the target gain does not match between channels +@end table +@end table + @section hflip Flip the input video horizontally. diff --git a/libavfilter/af_hdcd.c b/libavfilter/af_hdcd.c index 610dd9e..e4e37e2 100644 --- a/libavfilter/af_hdcd.c +++ b/libavfilter/af_hdcd.c @@ -870,6 +870,26 @@ static const char * const pe_str[] = { * 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 +#define HDCD_ANA_OFF 0 +#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, + HDCD_ANA_PE_DESC, + HDCD_ANA_CDT_DESC, + HDCD_ANA_TGM_DESC, +}; + typedef struct HDCDContext { const AVClass *class; hdcd_state_t state[HDCD_MAX_CHANNELS]; @@ -885,6 +905,12 @@ typedef struct HDCDContext { * default is off */ int force_pe; + /* analyze mode replaces the audio with a solid tone and adjusts + * 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 */ + /* config_input() and config_output() scan links for any resampling * or format changes. If found, warnings are issued and bad_config * is set. */ @@ -909,6 +935,13 @@ static const AVOption hdcd_options[] = { OFFSET(process_stereo), AV_OPT_TYPE_BOOL, { .i64 = HDCD_PROCESS_STEREO_DEFAULT }, 0, 1, A }, { "force_pe", "Always extend peaks above -3dBFS even when PE is not signaled.", OFFSET(force_pe), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, A }, + { "analyze_mode", "Replace audio with solid tone and signal some processing aspect in the amplitude.", + OFFSET(analyze_mode), AV_OPT_TYPE_INT, { .i64=HDCD_ANA_OFF }, 0, HDCD_ANA_TOP-1, A, "analyze_mode"}, + { "off", HDCD_ANA_OFF_DESC, 0, AV_OPT_TYPE_CONST, {.i64=HDCD_ANA_OFF}, 0, 0, A, "analyze_mode" }, + { "lle", HDCD_ANA_LLE_DESC, 0, AV_OPT_TYPE_CONST, {.i64=HDCD_ANA_LLE}, 0, 0, A, "analyze_mode" }, + { "pe", HDCD_ANA_PE_DESC, 0, AV_OPT_TYPE_CONST, {.i64=HDCD_ANA_PE}, 0, 0, A, "analyze_mode" }, + { "cdt", HDCD_ANA_CDT_DESC, 0, AV_OPT_TYPE_CONST, {.i64=HDCD_ANA_CDT}, 0, 0, A, "analyze_mode" }, + { "tgm", HDCD_ANA_TGM_DESC, 0, AV_OPT_TYPE_CONST, {.i64=HDCD_ANA_TGM}, 0, 0, A, "analyze_mode" }, {NULL} }; @@ -1209,6 +1242,77 @@ static int hdcd_scan_stereo(HDCDContext *ctx, const int32_t *samples, int max) return result; } +/* 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) +{ + float sflt = sample, vv = v; + vv /= maxv; + if (vv > 1.0) vv = 1.0; + sflt *= 1.0 + (vv * 18); + return (int32_t)sflt; +} + +/* behaves like hdcd_envelope(), but encodes processing information in + * a way that is audible (and visible in an audio editor) to aid analysis. */ +static int hdcd_analyze(int32_t *samples, int count, int stride, int gain, int target_gain, int extend, int mode, int cdt_active, int tg_mismatch) +{ + static const int maxg = 0xf << 7; + int i; + int32_t *samples_end = samples + stride * count; + + for (i = 0; i < count; i++) { + samples[i * stride] <<= 15; + if (mode == HDCD_ANA_PE) { + int pel = (samples[i * stride] >> 16) & 1; + int32_t sample = samples[i * stride]; + samples[i * stride] = hdcd_analyze_gen(sample, !!(pel && extend), 1); + } else if (mode == HDCD_ANA_TGM && tg_mismatch > 0) + samples[i * stride] = hdcd_analyze_gen(samples[i * stride], 1, 1); + else if (mode == HDCD_ANA_CDT && cdt_active) + samples[i * stride] = hdcd_analyze_gen(samples[i * stride], 1, 1); + } + + if (gain <= target_gain) { + int len = FFMIN(count, target_gain - gain); + /* attenuate slowly */ + for (i = 0; i < len; i++) { + ++gain; + if (mode == HDCD_ANA_LLE) + *samples = hdcd_analyze_gen(*samples, gain, maxg); + samples += stride; + } + count -= len; + } else { + int len = FFMIN(count, (gain - target_gain) >> 3); + /* amplify quickly */ + for (i = 0; i < len; i++) { + gain -= 8; + if (mode == HDCD_ANA_LLE) + *samples = hdcd_analyze_gen(*samples, gain, maxg); + samples += stride; + } + if (gain - 8 < target_gain) + gain = target_gain; + count -= len; + } + + /* hold a steady level */ + if (gain == 0) { + if (count > 0) + samples += count * stride; + } else { + while (--count >= 0) { + if (mode == HDCD_ANA_LLE) + *samples = hdcd_analyze_gen(*samples, gain, maxg); + samples += stride; + } + } + + av_assert0(samples == samples_end); + + return gain; +} + static int hdcd_envelope(int32_t *samples, int count, int stride, int gain, int target_gain, int extend) { int i; @@ -1316,7 +1420,10 @@ static void hdcd_process(HDCDContext *ctx, hdcd_state_t *state, int32_t *samples envelope_run = run - 1; av_assert0(samples + envelope_run * stride <= samples_end); - gain = hdcd_envelope(samples, envelope_run, stride, gain, target_gain, peak_extend); + if (ctx->analyze_mode) + gain = hdcd_analyze(samples, envelope_run, stride, gain, target_gain, peak_extend, ctx->analyze_mode, state->sustain, -1); + else + gain = hdcd_envelope(samples, envelope_run, stride, gain, target_gain, peak_extend); samples += envelope_run * stride; count -= envelope_run; @@ -1325,7 +1432,10 @@ static void hdcd_process(HDCDContext *ctx, hdcd_state_t *state, int32_t *samples } if (lead > 0) { av_assert0(samples + lead * stride <= samples_end); - gain = hdcd_envelope(samples, lead, stride, gain, target_gain, peak_extend); + if (ctx->analyze_mode) + gain = hdcd_analyze(samples, lead, stride, gain, target_gain, peak_extend, ctx->analyze_mode, state->sustain, -1); + else + gain = hdcd_envelope(samples, lead, stride, gain, target_gain, peak_extend); } state->running_gain = gain; @@ -1339,7 +1449,7 @@ static void hdcd_process_stereo(HDCDContext *ctx, int32_t *samples, int count) int peak_extend[2]; int lead = 0; - hdcd_control_stereo(ctx, &peak_extend[0], &peak_extend[1]); + int ctlret = hdcd_control_stereo(ctx, &peak_extend[0], &peak_extend[1]); while (count > lead) { int envelope_run, run; @@ -1349,7 +1459,16 @@ static void hdcd_process_stereo(HDCDContext *ctx, int32_t *samples, int count) av_assert0(samples + envelope_run * stride <= samples_end); - if (envelope_run) { + if (ctx->analyze_mode) { + gain[0] = hdcd_analyze(samples, envelope_run, stride, gain[0], ctx->val_target_gain, peak_extend[0], + ctx->analyze_mode, + ctx->state[0].sustain, + (ctlret == HDCD_TG_MISMATCH) ); + gain[1] = hdcd_analyze(samples + 1, envelope_run, stride, gain[1], ctx->val_target_gain, peak_extend[1], + ctx->analyze_mode, + ctx->state[1].sustain, + (ctlret == HDCD_TG_MISMATCH) ); + } else { gain[0] = hdcd_envelope(samples, envelope_run, stride, gain[0], ctx->val_target_gain, peak_extend[0]); gain[1] = hdcd_envelope(samples + 1, envelope_run, stride, gain[1], ctx->val_target_gain, peak_extend[1]); } @@ -1358,18 +1477,32 @@ static void hdcd_process_stereo(HDCDContext *ctx, int32_t *samples, int count) count -= envelope_run; lead = run - envelope_run; - hdcd_control_stereo(ctx, &peak_extend[0], &peak_extend[1]); + ctlret = hdcd_control_stereo(ctx, &peak_extend[0], &peak_extend[1]); } if (lead > 0) { av_assert0(samples + lead * stride <= samples_end); - gain[0] = hdcd_envelope(samples, lead, stride, gain[0], ctx->val_target_gain, peak_extend[0]); - gain[1] = hdcd_envelope(samples + 1, lead, stride, gain[1], ctx->val_target_gain, peak_extend[1]); + if (ctx->analyze_mode) { + gain[0] = hdcd_analyze(samples, lead, stride, gain[0], ctx->val_target_gain, peak_extend[0], + ctx->analyze_mode, + ctx->state[0].sustain, + (ctlret == HDCD_TG_MISMATCH) ); + gain[1] = hdcd_analyze(samples + 1, lead, stride, gain[1], ctx->val_target_gain, peak_extend[1], + ctx->analyze_mode, + ctx->state[1].sustain, + (ctlret == HDCD_TG_MISMATCH) ); + } else { + gain[0] = hdcd_envelope(samples, lead, stride, gain[0], ctx->val_target_gain, peak_extend[0]); + gain[1] = hdcd_envelope(samples + 1, lead, stride, gain[1], ctx->val_target_gain, peak_extend[1]); + } } ctx->state[0].running_gain = gain[0]; 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 int filter_frame(AVFilterLink *inlink, AVFrame *in) { AVFilterContext *ctx = inlink->dst; @@ -1390,8 +1523,21 @@ static int filter_frame(AVFilterLink *inlink, AVFrame *in) in_data = (int16_t*)in->data[0]; out_data = (int32_t*)out->data[0]; - for (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]) - 0x5981 >= 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 */ @@ -1557,6 +1703,7 @@ static av_cold int init(AVFilterContext *ctx) (s->process_stereo) ? "process stereo channels together" : "process each channel separately"); av_log(ctx, AV_LOG_VERBOSE, "Force PE: %s\n", (s->force_pe) ? "on" : "off"); + av_log(ctx, AV_LOG_VERBOSE, "Analyze mode: [%d] %s\n", s->analyze_mode, ana_mode_str[s->analyze_mode] ); return 0; } |