From 0891b4e6a8a2b8854dbb1bbd8ed350e320f123fe Mon Sep 17 00:00:00 2001 From: Leonid Pershin Date: Fri, 24 Jul 2026 23:08:10 +0300 Subject: [PATCH] Add loudness normalization settings: introduce NormalizeLoudness and LoudnessTargetLufs options in configuration files, update FfmpegMediaProcessor to apply loudness normalization during media processing, and document changes in media storage guide. --- .env.example | 4 ++++ backend/src/TeleWave.Api/appsettings.json | 4 +++- .../Media/FfmpegMediaProcessor.cs | 16 +++++++++++++++- .../Media/MediaOptions.cs | 7 +++++++ docs/media-storage-and-streaming.md | 6 +++++- 5 files changed, 34 insertions(+), 3 deletions(-) diff --git a/.env.example b/.env.example index 267a27f..418742a 100644 --- a/.env.example +++ b/.env.example @@ -48,6 +48,10 @@ Media__MaxUploadBytes=21474836480 Media__TranscodeThreads=3 # Период опроса каталога inbox/ сканером, секунды. Media__InboxScanSeconds=15 +# Нормализация громкости при обработке (EBU R128 loudnorm) — все ролики одинаковой громкости. +Media__NormalizeLoudness=true +# Целевая громкость, LUFS (−16 типично для стриминга; тише — уменьшить). +Media__LoudnessTargetLufs=-16 # ── ASP.NET Core ────────────────────────────────────────────────────────── ASPNETCORE_ENVIRONMENT=Production diff --git a/backend/src/TeleWave.Api/appsettings.json b/backend/src/TeleWave.Api/appsettings.json index 085ee8c..051c009 100644 --- a/backend/src/TeleWave.Api/appsettings.json +++ b/backend/src/TeleWave.Api/appsettings.json @@ -30,7 +30,9 @@ "FfprobePath": "ffprobe", "MaxUploadBytes": 21474836480, "TranscodeThreads": 3, - "InboxScanSeconds": 15 + "InboxScanSeconds": 15, + "NormalizeLoudness": true, + "LoudnessTargetLufs": -16 }, "Serilog": { "Using": [ "Serilog.Sinks.Console" ], diff --git a/backend/src/TeleWave.Infrastructure/Media/FfmpegMediaProcessor.cs b/backend/src/TeleWave.Infrastructure/Media/FfmpegMediaProcessor.cs index 3b11e88..9c9b3eb 100644 --- a/backend/src/TeleWave.Infrastructure/Media/FfmpegMediaProcessor.cs +++ b/backend/src/TeleWave.Infrastructure/Media/FfmpegMediaProcessor.cs @@ -124,10 +124,24 @@ public sealed class FfmpegMediaProcessor( "48000", }; + // Аудиофильтры: нормализация громкости (EBU R128, чтобы все ролики звучали одинаково) и, + // при добивке хвоста, тишина apad. Порядок: сначала loudnorm по реальному звуку, потом apad. + var afilters = new List(); + if (_media.NormalizeLoudness) + afilters.Add( + $"loudnorm=I={_media.LoudnessTargetLufs.ToString(CultureInfo.InvariantCulture)}:TP=-1.5:LRA=11" + ); if (padding) + afilters.Add($"apad=pad_dur={Fmt(pad)}"); + + if (afilters.Count > 0) { args.Add("-af"); - args.Add($"apad=pad_dur={Fmt(pad)}"); + args.Add(string.Join(",", afilters)); + } + + if (padding) + { args.Add("-t"); args.Add(Fmt(target)); } diff --git a/backend/src/TeleWave.Infrastructure/Media/MediaOptions.cs b/backend/src/TeleWave.Infrastructure/Media/MediaOptions.cs index 1024fbc..46a922a 100644 --- a/backend/src/TeleWave.Infrastructure/Media/MediaOptions.cs +++ b/backend/src/TeleWave.Infrastructure/Media/MediaOptions.cs @@ -15,4 +15,11 @@ public sealed class MediaOptions /// Период опроса inbox/ сканером, секунды. public int InboxScanSeconds { get; init; } = 15; + + /// Нормализовать громкость при обработке (ffmpeg loudnorm, EBU R128) — чтобы все ролики + /// звучали одинаково. + public bool NormalizeLoudness { get; init; } = true; + + /// Целевая интегральная громкость, LUFS (−16 — типично для стриминга; тише — уменьшить). + public int LoudnessTargetLufs { get; init; } = -16; } diff --git a/docs/media-storage-and-streaming.md b/docs/media-storage-and-streaming.md index 7cf806f..ba20fae 100644 --- a/docs/media-storage-and-streaming.md +++ b/docs/media-storage-and-streaming.md @@ -182,6 +182,7 @@ nice -n 10 ffmpeg -i src \ -c:v libx264 -preset veryfast -crf 21 -maxrate 4500k -bufsize 9000k \ -vf "scale='min(1920,iw)':-2" \ -force_key_frames "expr:gte(t,n_forced*2)" -sc_threshold 0 \ + -af "loudnorm=I=-16:TP=-1.5:LRA=11" \ -c:a aac -b:a 128k -ac 2 -ar 48000 \ -f hls -hls_time 2 -hls_playlist_type vod -hls_list_size 0 \ -hls_segment_filename seg%05d.ts index.m3u8 @@ -191,7 +192,10 @@ nice -n 10 ffmpeg -i src \ - `-force_key_frames expr:gte(t,n_forced*2)` надёжнее фиксированного `-g` при нецелых fps. - `-sc_threshold 0` убирает сцен-детект, иначе сегменты перестанут быть ровными. - `maxrate 4500k` — ключевой кадр каждые 2с стоит ~5–10% битрейта. -- `-threads 3` + `nice` оставляют ядро эфиру. Финальный шаг — добивка хвоста до кратности 2с. +- `loudnorm=I=-16:TP=-1.5:LRA=11` — нормализация громкости (EBU R128), чтобы все ролики звучали + одинаково; цель настраивается (`Media__LoudnessTargetLufs`), можно выключить (`Media__NormalizeLoudness`). +- `-threads 3` + `nice` оставляют ядро эфиру. Финальный шаг — добивка хвоста до кратности 2с + (`-af ...,apad=pad_dur=PAD -t TARGET`). Очередь обработки: `System.Threading.Channels` + `BackgroundService`, строго одна задача за раз. Inbox-сканер — второй `BackgroundService`: замечает новый файл, ждёт стабилизации размера, заводит