A new filter that provides HDCD decoding, using libhdcd. https://github.com/bp0/libhdcd
Signed-off-by: Burt P <[email protected]> --- Changelog | 1 + configure | 4 + doc/filters.texi | 52 +++++++++ libavfilter/Makefile | 1 + libavfilter/af_hdcd.c | 267 +++++++++++++++++++++++++++++++++++++++++++++++ libavfilter/allfilters.c | 1 + 6 files changed, 326 insertions(+) create mode 100644 libavfilter/af_hdcd.c diff --git a/Changelog b/Changelog index 0d04f47..b6c8fdb 100644 --- a/Changelog +++ b/Changelog @@ -62,6 +62,7 @@ version <next>: - Intel QSV video scaling and deinterlacing filter - OpenH264 decoder wrapper - Removed the legacy X11 screen grabber, use XCB instead +- HDCD decoding filter through libhdcd version 11: diff --git a/configure b/configure index 9f836b7..6fedaef 100755 --- a/configure +++ b/configure @@ -197,6 +197,7 @@ External library support: --enable-libfontconfig font configuration and management --enable-libfreetype font rendering --enable-libgsm GSM audio encoding/decoding + --enable-libhdcd HDCD decoding filter --enable-libilbc ILBC audio encoding/decoding --enable-libkvazaar HEVC video encoding --enable-libmp3lame MP3 audio encoding @@ -1270,6 +1271,7 @@ EXTERNAL_LIBRARY_LIST=" libfontconfig libfreetype libgsm + libhdcd libilbc libkvazaar libmp3lame @@ -2426,6 +2428,7 @@ frei0r_filter_deps="frei0r dlopen" frei0r_filter_extralibs='$ldl' frei0r_src_filter_deps="frei0r dlopen" frei0r_src_filter_extralibs='$ldl' +hdcd_filter_deps="libhdcd" hqdn3d_filter_deps="gpl" interlace_filter_deps="gpl" ocv_filter_deps="libopencv" @@ -4604,6 +4607,7 @@ enabled libfreetype && require_pkg_config freetype2 "ft2build.h FT_FREETYP enabled libgsm && { for gsm_hdr in "gsm.h" "gsm/gsm.h"; do check_lib "${gsm_hdr}" gsm_create -lgsm && break; done || die "ERROR: libgsm not found"; } +enabled libhdcd && require_pkg_config libhdcd "hdcd/hdcd_decode2.h" hdcd_process enabled libilbc && require libilbc ilbc.h WebRtcIlbcfix_InitDecode -lilbc enabled libkvazaar && require_pkg_config "kvazaar >= 0.8.1" kvazaar.h kvz_api_get enabled libmfx && require_pkg_config libmfx "mfx/mfxvideo.h" MFXInit diff --git a/doc/filters.texi b/doc/filters.texi index 2651f17..a832630 100644 --- a/doc/filters.texi +++ b/doc/filters.texi @@ -647,6 +647,58 @@ avconv -i fl -i fr -i fc -i sl -i sr -i lfe -filter_complex out @end example +@section hdcd + +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 +avconv -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 +avconv -i HDCD16.wav -af hdcd OUT16.wav +avconv -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 resample Convert the audio sample format, sample rate and channel layout. It is not meant to be used directly; it is inserted automatically by libavfilter diff --git a/libavfilter/Makefile b/libavfilter/Makefile index dea8ffa..7f81f25 100644 --- a/libavfilter/Makefile +++ b/libavfilter/Makefile @@ -37,6 +37,7 @@ OBJS-$(CONFIG_COMPAND_FILTER) += af_compand.o OBJS-$(CONFIG_JOIN_FILTER) += af_join.o OBJS-$(CONFIG_RESAMPLE_FILTER) += af_resample.o OBJS-$(CONFIG_VOLUME_FILTER) += af_volume.o +OBJS-$(CONFIG_HDCD_FILTER) += af_hdcd.o OBJS-$(CONFIG_ANULLSINK_FILTER) += asink_anullsink.o OBJS-$(CONFIG_ANULLSRC_FILTER) += asrc_anullsrc.o diff --git a/libavfilter/af_hdcd.c b/libavfilter/af_hdcd.c new file mode 100644 index 0000000..e96d36e --- /dev/null +++ b/libavfilter/af_hdcd.c @@ -0,0 +1,267 @@ +/* + * This file is part of Libav. + * + * Libav is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * Libav is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with Libav; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * HDCD decoding filter + */ + +#include "libavutil/opt.h" +#include "libavutil/avassert.h" +#include "libavutil/channel_layout.h" +#include "formats.h" +#include "avfilter.h" +#include "internal.h" +#include "audio.h" +#include <hdcd/hdcd_decode2.h> + +#define HDCD_PROCESS_STEREO_DEFAULT 1 +#define HDCD_MAX_CHANNELS 2 + +typedef struct HDCDContext { + const AVClass *class; + + hdcd_state_t state[HDCD_MAX_CHANNELS]; /**< while using n-channel mode */ + hdcd_state_stereo_t state_stereo; /**< while using stereo mode */ + + hdcd_detection_data_t detect; /**< HDCD detection data */ + + /* AVOption members */ + /** use hdcd_*_stereo() functions to process both channels together. + * -af hdcd=process_stereo=0 for off + * -af hdcd=process_stereo=1 for on + * default is HDCD_PROCESS_STEREO_DEFAULT */ + int process_stereo; + /** always extend peaks above -3dBFS even if PE isn't signaled + * -af hdcd=force_pe=0 for off + * -af hdcd=force_pe=1 for on + * 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 cdt_ms; /**< code detect timer period in ms */ + /* end AVOption members */ + + hdcd_log_t logger; +} HDCDContext; + +#define OFFSET(x) offsetof(HDCDContext, x) +#define A AV_OPT_FLAG_AUDIO_PARAM +#define HDCD_ANA_MAX 4 +static const AVOption hdcd_options[] = { + { "process_stereo", "Process stereo channels together. Only apply target_gain when both channels match.", + OFFSET(process_stereo), AV_OPT_TYPE_INT, { .i64 = HDCD_PROCESS_STEREO_DEFAULT }, 0, 1, A }, + { "cdt_ms", "Code detect timer period in ms.", + OFFSET(cdt_ms), AV_OPT_TYPE_INT, { .i64 = 2000 }, 100, 60000, A }, + { "force_pe", "Always extend peaks above -3dBFS even when PE is not signaled.", + OFFSET(force_pe), AV_OPT_TYPE_INT, { .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_MAX, 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} +}; + +static const AVClass hdcd_class = { + .class_name = "hdcd filter", + .item_name = av_default_item_name, + .option = hdcd_options, + .version = LIBAVUTIL_VERSION_INT, +}; + +static int filter_frame(AVFilterLink *inlink, AVFrame *in) +{ + AVFilterContext *ctx = inlink->dst; + HDCDContext *s = ctx->priv; + AVFilterLink *outlink = ctx->outputs[0]; + AVFrame *out; + const int16_t *in_data; + int32_t *out_data; + int n, c, result; + int channel_count = av_get_channel_layout_nb_channels(in->channel_layout); + + out = ff_get_audio_buffer(outlink, in->nb_samples); + if (!out) { + av_frame_free(&in); + return AVERROR(ENOMEM); + } + result = av_frame_copy_props(out, in); + if (result) { + av_frame_free(&in); + return result; + } + + in_data = (int16_t*)in->data[0]; + out_data = (int32_t*)out->data[0]; + for (n = 0; n < in->nb_samples * channel_count; n++) + out_data[n] = in_data[n]; + + if (s->process_stereo) { + hdcd_process_stereo(&s->state_stereo, out_data, in->nb_samples); + hdcd_detect_stereo(&s->state_stereo, &s->detect); + } else { + hdcd_detect_start(&s->detect); + for (c = 0; c < channel_count; c++) { + hdcd_process(&s->state[c], out_data + c, in->nb_samples, channel_count); + hdcd_detect_onech(&s->state[c], &s->detect); + } + hdcd_detect_end(&s->detect, channel_count); + } + + av_frame_free(&in); + return ff_filter_frame(outlink, out); +} + +static int query_formats(AVFilterContext *ctx) +{ + AVFilterFormats *in_formats; + AVFilterFormats *out_formats; + AVFilterFormats *sample_rates = NULL; + AVFilterChannelLayouts *layouts = NULL; + AVFilterLink *inlink = ctx->inputs[0]; + AVFilterLink *outlink = ctx->outputs[0]; + + static const enum AVSampleFormat sample_fmts_in[] = { + AV_SAMPLE_FMT_S16, + AV_SAMPLE_FMT_NONE + }; + static const enum AVSampleFormat sample_fmts_out[] = { + AV_SAMPLE_FMT_S32, + AV_SAMPLE_FMT_NONE + }; + + ff_add_channel_layout(&layouts, AV_CH_LAYOUT_STEREO); + + ff_set_common_channel_layouts(ctx, layouts); + + in_formats = ff_make_format_list(sample_fmts_in); + out_formats = ff_make_format_list(sample_fmts_out); + if (!in_formats || !out_formats) + return AVERROR(ENOMEM); + + ff_formats_ref(in_formats, &inlink->out_formats); + ff_formats_ref(out_formats, &outlink->in_formats); + + ff_add_format(&sample_rates, 44100); + ff_set_common_samplerates(ctx, sample_rates); + return 0; +} + +static av_cold void uninit(AVFilterContext *ctx) +{ + HDCDContext *s = ctx->priv; + int i; + char detect_str[256] = ""; + + /* dump the state for each channel for AV_LOG_VERBOSE */ + if (s->process_stereo) { + hdcd_dump_state_to_log(&s->state_stereo.channel[0], 0); + hdcd_dump_state_to_log(&s->state_stereo.channel[1], 1); + } else { + for (i = 0; i < HDCD_MAX_CHANNELS; i++) + hdcd_dump_state_to_log(&s->state[0], i); + } + + /* log the HDCD decode information */ + hdcd_detect_str(&s->detect, detect_str, sizeof(detect_str)); + av_log(ctx, AV_LOG_INFO, "%s\n", detect_str); +} + +/** callback for error logging in hdcd_* */ +static void af_hdcd_log(const void *priv, const char* fmt, va_list args) +{ + av_vlog((AVFilterContext *)priv, AV_LOG_VERBOSE, fmt, args); +} + +static av_cold int init(AVFilterContext *ctx) +{ + HDCDContext *s = ctx->priv; + int c; + uint8_t flags = HDCD_FLAG_TGM_LOG_OFF; + + hdcd_log_init_ext(&s->logger, af_hdcd_log, (void*)ctx); + + if (s->force_pe) flags |= HDCD_FLAG_FORCE_PE; + hdcd_reset_stereo_ext(&s->state_stereo, 44100, s->cdt_ms, flags, s->analyze_mode, &s->logger); + for (c = 0; c < HDCD_MAX_CHANNELS; c++) { + hdcd_reset_ext(&s->state[c], 44100, s->cdt_ms, flags, s->analyze_mode, &s->logger); + } + hdcd_detect_reset(&s->detect); + + av_log(ctx, AV_LOG_VERBOSE, "CDT period: %dms (%d samples @44100Hz)\n", + s->cdt_ms, s->cdt_ms*44100/1000 ); + av_log(ctx, AV_LOG_VERBOSE, "Process mode: %s\n", + (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, hdcd_str_ana_mode(s->analyze_mode) ); + + return 0; +} + +static int config_input(AVFilterLink *inlink) +{ + AVFilterContext *ctx = inlink->dst; + HDCDContext *s = ctx->priv; + int channel_count = av_get_channel_layout_nb_channels(inlink->channel_layout); + + if (channel_count != 2 && s->process_stereo) { + av_log(ctx, AV_LOG_WARNING, "process_stereo disabled (channels = %d)", channel_count); + s->process_stereo = 0; + } + return 0; +} + +static const AVFilterPad avfilter_af_hdcd_inputs[] = { + { + .name = "default", + .type = AVMEDIA_TYPE_AUDIO, + .filter_frame = filter_frame, + .config_props = config_input, + }, + { NULL } +}; + +static const AVFilterPad avfilter_af_hdcd_outputs[] = { + { + .name = "default", + .type = AVMEDIA_TYPE_AUDIO, + }, + { NULL } +}; + +AVFilter ff_af_hdcd = { + .name = "hdcd", + .description = NULL_IF_CONFIG_SMALL("Apply High Definition Compatible Digital (HDCD) decoding."), + .priv_size = sizeof(HDCDContext), + .priv_class = &hdcd_class, + .init = init, + .uninit = uninit, + .query_formats = query_formats, + .inputs = avfilter_af_hdcd_inputs, + .outputs = avfilter_af_hdcd_outputs, +}; + diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c index de49d65..c5345ea 100644 --- a/libavfilter/allfilters.c +++ b/libavfilter/allfilters.c @@ -60,6 +60,7 @@ void avfilter_register_all(void) REGISTER_FILTER(JOIN, join, af); REGISTER_FILTER(RESAMPLE, resample, af); REGISTER_FILTER(VOLUME, volume, af); + REGISTER_FILTER(HDCD, hdcd, af); REGISTER_FILTER(ANULLSRC, anullsrc, asrc); -- 2.7.4 _______________________________________________ libav-devel mailing list [email protected] https://lists.libav.org/mailman/listinfo/libav-devel
