summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--doc/filters.texi32
-rw-r--r--libavfilter/af_hdcd.c163
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;
}
OpenPOWER on IntegriCloud