|
@@ -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,
|