PR #23729 opened by Z2697 URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23729 Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23729.patch
# Summary of changes Signal type hinting is an old feature but FFmpeg lacks support. Restricted SILK/CELT applications are new. QEXT is a new feature, a `libopus_96k` wrapper encoder is added, so that the users won't suddenly have 96k Opus when they assume everything to be resampled to 48k. (QEXT can still be enabled for 48k and provide higher max bitrate and no-cutoff, plus disabling dc_reject filter [ref](https://github.com/xiph/opus/issues/434#issuecomment-3447926221)) However, due to so many hardcoded `48000` in all the codes (not just FFmpeg), it's very hard to decode 96k Opus. I wrote a raw muxer for Opus (not included in PR) which outputs streams opus_demo can read and confirmed the result. <!-- If this PR requires new FATE test samples, attach them to the PR and list their target paths below (relative to the fate-suite root). Attached filenames must match the sample's filename: ```fate-samples # e.g. vorbis/new-sample.ogg ``` --> >From 6ac88475967279db0dc0cdc39832212d426d6435 Mon Sep 17 00:00:00 2001 From: 55087 <55087@DESKTOP-6M501RP> Date: Tue, 7 Jul 2026 19:55:56 +0800 Subject: [PATCH] avcodec/libopusenc.c: add new options for new and old features --- libavcodec/allcodecs.c | 1 + libavcodec/libopusenc.c | 76 +++++++++++++++++++++++++++++++++++++++-- 2 files changed, 75 insertions(+), 2 deletions(-) diff --git a/libavcodec/allcodecs.c b/libavcodec/allcodecs.c index e1668f1e80..4be7147d02 100644 --- a/libavcodec/allcodecs.c +++ b/libavcodec/allcodecs.c @@ -798,6 +798,7 @@ extern const FFCodec ff_libopencore_amrnb_decoder; extern const FFCodec ff_libopencore_amrwb_decoder; extern const FFCodec ff_libopenjpeg_encoder; extern const FFCodec ff_libopus_encoder; +extern const FFCodec ff_libopus_96k_encoder; extern const FFCodec ff_libopus_decoder; extern const FFCodec ff_librav1e_encoder; extern const FFCodec ff_librsvg_decoder; diff --git a/libavcodec/libopusenc.c b/libavcodec/libopusenc.c index 394911ff36..dca1ab947f 100644 --- a/libavcodec/libopusenc.c +++ b/libavcodec/libopusenc.c @@ -44,9 +44,13 @@ typedef struct LibopusEncOpts { int max_bandwidth; int mapping_family; int dtx; + int signal; #ifdef OPUS_SET_PHASE_INVERSION_DISABLED_REQUEST int apply_phase_inv; #endif +#ifdef OPUS_SET_QEXT_REQUEST + int qext; +#endif } LibopusEncOpts; typedef struct LibopusEncContext { @@ -123,6 +127,14 @@ static int libopus_configure_encoder(AVCodecContext *avctx, OpusMSEncoder *enc, return AVERROR(EINVAL); } + if (!strcmp(avctx->codec->name, "libopus_96k")) + opts->qext = 1; + if (!opts->qext && avctx->sample_rate > 48000) { + av_log(avctx, AV_LOG_ERROR, + "96 kHz require QEXT.\n"); + return AVERROR(EINVAL); + } + ret = opus_multistream_encoder_ctl(enc, OPUS_SET_BITRATE(avctx->bit_rate)); if (ret != OPUS_OK) { av_log(avctx, AV_LOG_ERROR, @@ -130,6 +142,13 @@ static int libopus_configure_encoder(AVCodecContext *avctx, OpusMSEncoder *enc, return ret; } + ret = opus_multistream_encoder_ctl(enc, OPUS_SET_SIGNAL(opts->signal)); + if (ret != OPUS_OK) { + av_log(avctx, AV_LOG_ERROR, + "Failed to set signal: %s\n", opus_strerror(ret)); + return ret; + } + ret = opus_multistream_encoder_ctl(enc, OPUS_SET_COMPLEXITY(opts->complexity)); if (ret != OPUS_OK) @@ -184,6 +203,15 @@ static int libopus_configure_encoder(AVCodecContext *avctx, OpusMSEncoder *enc, "Unable to set phase inversion: %s\n", opus_strerror(ret)); #endif + +#ifdef OPUS_SET_QEXT_REQUEST + ret = opus_multistream_encoder_ctl(enc, + OPUS_SET_QEXT(opts->qext)); + if (ret != OPUS_OK) + av_log(avctx, AV_LOG_WARNING, + "Unable to set qext: %s\n", + opus_strerror(ret)); +#endif return OPUS_OK; } @@ -299,6 +327,8 @@ static av_cold int libopus_encode_init(AVCodecContext *avctx) #endif opus->opts.packet_size = avctx->frame_size = frame_size * avctx->sample_rate / 48000; + if (avctx->sample_rate == 96000) + frame_size *= 2; break; default: av_log(avctx, AV_LOG_ERROR, "Invalid frame duration: %g.\n" @@ -396,10 +426,10 @@ static av_cold int libopus_encode_init(AVCodecContext *avctx) "No bit rate set. Defaulting to %"PRId64" bps.\n", avctx->bit_rate); } - if (avctx->bit_rate < 500 || avctx->bit_rate > 256000 * channels) { + if (avctx->bit_rate < 500 || avctx->bit_rate > 750000 * channels) { av_log(avctx, AV_LOG_ERROR, "The bit rate %"PRId64" bps is unsupported. " "Please choose a value between 500 and %d.\n", avctx->bit_rate, - 256000 * channels); + 750000 * channels); ret = AVERROR(EINVAL); goto fail; } @@ -554,10 +584,22 @@ static av_cold int libopus_encode_close(AVCodecContext *avctx) #define OFFSET(x) offsetof(LibopusEncContext, opts.x) #define FLAGS AV_OPT_FLAG_AUDIO_PARAM | AV_OPT_FLAG_ENCODING_PARAM static const AVOption libopus_options[] = { +#ifdef OPUS_APPLICATION_RESTRICTED_CELT + { "application", "Intended application type", OFFSET(application), AV_OPT_TYPE_INT, { .i64 = OPUS_APPLICATION_AUDIO }, OPUS_APPLICATION_VOIP, OPUS_APPLICATION_RESTRICTED_CELT, FLAGS, .unit = "application" }, +#else { "application", "Intended application type", OFFSET(application), AV_OPT_TYPE_INT, { .i64 = OPUS_APPLICATION_AUDIO }, OPUS_APPLICATION_VOIP, OPUS_APPLICATION_RESTRICTED_LOWDELAY, FLAGS, .unit = "application" }, +#endif { "voip", "Favor improved speech intelligibility", 0, AV_OPT_TYPE_CONST, { .i64 = OPUS_APPLICATION_VOIP }, 0, 0, FLAGS, .unit = "application" }, { "audio", "Favor faithfulness to the input", 0, AV_OPT_TYPE_CONST, { .i64 = OPUS_APPLICATION_AUDIO }, 0, 0, FLAGS, .unit = "application" }, { "lowdelay", "Restrict to only the lowest delay modes, disable voice-optimized modes", 0, AV_OPT_TYPE_CONST, { .i64 = OPUS_APPLICATION_RESTRICTED_LOWDELAY }, 0, 0, FLAGS, .unit = "application" }, +#ifdef OPUS_APPLICATION_RESTRICTED_CELT + { "silk", "Restrict to only the silk mode", 0, AV_OPT_TYPE_CONST, { .i64 = OPUS_APPLICATION_RESTRICTED_SILK }, 0, 0, FLAGS, .unit = "application" }, + { "celt", "Restrict to only the celt mode", 0, AV_OPT_TYPE_CONST, { .i64 = OPUS_APPLICATION_RESTRICTED_CELT }, 0, 0, FLAGS, .unit = "application" }, +#endif + { "signal", "A hint which helps the encoder's mode selection.", OFFSET(signal), AV_OPT_TYPE_INT, { .i64 = OPUS_AUTO }, OPUS_AUTO, OPUS_SIGNAL_MUSIC, FLAGS, .unit = "signal" }, + { "auto", "Default of libopus", 0, AV_OPT_TYPE_CONST, { .i64 = OPUS_AUTO }, 0, 0, FLAGS, .unit = "signal" }, + { "voice", "Bias thresholds towards choosing LPC or Hybrid modes", 0, AV_OPT_TYPE_CONST, { .i64 = OPUS_SIGNAL_VOICE }, 0, 0, FLAGS, .unit = "signal" }, + { "music", "Bias thresholds towards choosing MDCT modes", 0, AV_OPT_TYPE_CONST, { .i64 = OPUS_SIGNAL_MUSIC }, 0, 0, FLAGS, .unit = "signal" }, { "frame_duration", "Duration of a frame in milliseconds", OFFSET(frame_duration), AV_OPT_TYPE_FLOAT, { .dbl = 20.0 }, 2.5, 120.0, FLAGS }, { "packet_loss", "Expected packet loss percentage", OFFSET(packet_loss), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 100, FLAGS }, { "fec", "Enable inband FEC. Expected packet loss must be non-zero", OFFSET(fec), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, FLAGS }, @@ -569,6 +611,9 @@ static const AVOption libopus_options[] = { { "dtx", "Enable DTX (Discontinuous transmission)", OFFSET(dtx), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, FLAGS }, #ifdef OPUS_SET_PHASE_INVERSION_DISABLED_REQUEST { "apply_phase_inv", "Apply intensity stereo phase inversion", OFFSET(apply_phase_inv), AV_OPT_TYPE_BOOL, { .i64 = 1 }, 0, 1, FLAGS }, +#endif +#ifdef OPUS_SET_QEXT_REQUEST + { "qext", "Set QEXT (96 kHz, higher bitrates, no 20 kHz cutoff when bitrate is enough)", OFFSET(qext), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, FLAGS }, #endif { NULL }, }; @@ -590,6 +635,12 @@ static const int libopus_sample_rates[] = { 48000, 24000, 16000, 12000, 8000, 0, }; +#ifdef OPUS_SET_QEXT_REQUEST +static const int libopus_96k_sample_rates[] = { + 96000, 0, +}; +#endif + const FFCodec ff_libopus_encoder = { .p.name = "libopus", CODEC_LONG_NAME("libopus Opus"), @@ -608,3 +659,24 @@ const FFCodec ff_libopus_encoder = { .defaults = libopus_defaults, .p.wrapper_name = "libopus", }; + +#ifdef OPUS_SET_QEXT_REQUEST +const FFCodec ff_libopus_96k_encoder = { + .p.name = "libopus_96k", + CODEC_LONG_NAME("libopus Opus 96 kHZ"), + .p.type = AVMEDIA_TYPE_AUDIO, + .p.id = AV_CODEC_ID_OPUS, + .p.capabilities = AV_CODEC_CAP_DR1 | AV_CODEC_CAP_DELAY | + AV_CODEC_CAP_SMALL_LAST_FRAME, + .caps_internal = FF_CODEC_CAP_NOT_INIT_THREADSAFE, + .priv_data_size = sizeof(LibopusEncContext), + .init = libopus_encode_init, + FF_CODEC_ENCODE_CB(libopus_encode), + .close = libopus_encode_close, + CODEC_SAMPLEFMTS(AV_SAMPLE_FMT_S16, AV_SAMPLE_FMT_FLT), + CODEC_SAMPLERATES_ARRAY(libopus_96k_sample_rates), + .p.priv_class = &libopus_class, + .defaults = libopus_defaults, + .p.wrapper_name = "libopus", +}; +#endif -- 2.52.0 _______________________________________________ ffmpeg-devel mailing list -- [email protected] To unsubscribe send an email to [email protected]
