LUFS 及其重要性

1.1、什么是 LUFS?

LUFS(Loudness Units relative to Full Scale)是音频工程中用于测量感知响度的标准单位。它已成为广播、流媒体和音乐制作领域的行业标准,用于确保不同音频内容具有一致的响度水平。

LUFS 是 ITU-R BS.1770 标准的核心概念,该标准由国际电信联盟制定,旨在解决所谓的"响度战争"问题 - 即不同节目或歌曲之间响度差异过大的现象。

LUFS 的重要性

  1. 一致性体验:确保观众在不同节目间切换时不需要频繁调整音量。
  2. 保护听力:防止过度压缩和过大的响度对听众听力造成损害。
  3. 提升音质:避免过度压缩导致的音频质量下降。
  4. 行业标准:被 Netflix、YouTube、Spotify 等平台广泛采用。

1.2、为什么要使用 LUFS?

使用 LUFS 作为响度测量标准的原因包括:

  1. 人耳感知:LUFS 考虑了人耳对不同频率的敏感度,提供了更符合实际听感的响度测量。
  2. 一致性:在不同平台和设备上,LUFS 提供了一致的响度标准,确保用户在不同内容之间的体验一致。
  3. 避免响度战争:通过使用 LUFS,制作人和工程师可以避免过度压缩音频以提高响度,从而保护音质和听力。
  4. 行业认可:LUFS 已成为广播和流媒体行业的标准,使用 LUFS 可以确保内容符合行业要求。

1.3、LUFS 和 dBFS 的区别

  • LUFS(Loudness Units relative to Full Scale)

    • LUFS 是一种测量感知响度的单位,考虑了人耳对不同频率的敏感度。
    • LUFS 值通常是负值,0 LUFS 表示满刻度(Full Scale),即信号的最大值。
    • LUFS 适用于评估音频内容的响度,特别是在广播和流媒体中。
  • dBFS(Decibels Full Scale)

    • dBFS 是一种测量信号幅度的单位,表示相对于数字音频系统的最大可能值(满刻度)的分贝值。
    • dBFS 值也是负值,0 dBFS 表示满刻度,负值表示低于满刻度的信号。
    • dBFS 主要用于测量信号的幅度,而不是感知响度。

LUFS 和 dBFS 都是音频工程中重要的测量单位,但它们的用途和意义不同。LUFS 更关注人耳的感知响度,而 dBFS 则关注信号的幅度。使用 LUFS 可以帮助音频制作人和工程师创建更一致和高质量的音频内容。

二、LUFS 的计算原理

ITU-R BS.1770 标准定义了计算 LUFS 的四个关键步骤:

1. K 加权滤波
K 加权滤波器模拟人耳对不同频率的敏感度,包含两个部分:
• 高频架式滤波器:提升高频部分(约 1500Hz)
• 高通滤波器:衰减低频部分(约 38Hz)
2. 分块处理
音频被分割成固定长度的块(通常为 400ms),相邻块之间有 75% 的重叠。这种重叠处理确保了响度计算的稳定性。
3. 门限处理
计算包含两个门限:
• 绝对门限:-70 LUFS,低于此值的块被忽略
• 相对门限:比超过绝对门限的块的平均响度低 10dB
4. 集成响度计算
只考虑超过两个门限的音频块,计算它们的平均响度值作为最终结果。

三、Python 实现 LUFS 计算

以下是完整的 Python 实现代码,基于 ITU-R BS.1770-4 标准:

import numpy as np
from scipy import signal
import warnings
import soundfile as sf


class IIRFilter:
    """IIR滤波器实现"""

    def __init__(self, G, Q, fc, rate, filter_type, passband_gain=1.0):
        self.G = G
        self.Q = Q
        self.fc = fc
        self.rate = rate
        self.filter_type = filter_type
        self.passband_gain = passband_gain
        self.b, self.a = self.generate_coefficients()

    def generate_coefficients(self):
        """生成滤波器系数"""
        A = 10 ** (self.G / 40.0)
        w0 = 2.0 * np.pi * (self.fc / self.rate)
        alpha = np.sin(w0) / (2.0 * self.Q)

        if self.filter_type == 'high_shelf':
            b0 = A * ((A + 1) + (A - 1) * np.cos(w0) + 2 * np.sqrt(A) * alpha)
            b1 = -2 * A * ((A - 1) + (A + 1) * np.cos(w0))
            b2 = A * ((A + 1) + (A - 1) * np.cos(w0) - 2 * np.sqrt(A) * alpha)
            a0 = (A + 1) - (A - 1) * np.cos(w0) + 2 * np.sqrt(A) * alpha
            a1 = 2 * ((A - 1) - (A + 1) * np.cos(w0))
            a2 = (A + 1) - (A - 1) * np.cos(w0) - 2 * np.sqrt(A) * alpha
        elif self.filter_type == 'high_shelf_DeMan':
            K = np.tan(np.pi * self.fc / self.rate)
            Vh = np.power(10.0, self.G / 20.0)
            Vb = np.power(Vh, 0.499666774155)
            a0_ = 1.0 + K / self.Q + K * K
            b0 = (Vh + Vb * K / self.Q + K * K) / a0_
            b1 = 2.0 * (K * K - Vh) / a0_
            b2 = (Vh - Vb * K / self.Q + K * K) / a0_
            a0 = 1.0
            a1 = 2.0 * (K * K - 1.0) / a0_
            a2 = (1.0 - K / self.Q + K * K) / a0_
        elif self.filter_type == 'high_pass_DeMan':
            K = np.tan(np.pi * self.fc / self.rate)
            a0_ = 1.0 + K / self.Q + K * K
            b0 = 1.0
            b1 = -2.0
            b2 = 1.0
            a0 = 1.0
            a1 = 2.0 * (K * K - 1.0) / a0_
            a2 = (1.0 - K / self.Q + K * K) / a0_
        else:
            raise ValueError(f"不支持的滤波器类型: {self.filter_type}")

        return np.array([b0, b1, b2]), np.array([a0, a1, a2])

    def apply_filter(self, data):
        """应用滤波器到音频数据"""
        return self.passband_gain * signal.lfilter(self.b, self.a, data)


class LoudnessMeter:
    """响度计实现"""

    def __init__(self, rate, block_size=0.400):
        self.rate = rate
        self.block_size = block_size
        self._setup_filters()

    def _setup_filters(self):
        """设置DeMan滤波器"""
        self.filters = {
            'high_shelf': IIRFilter(
                G=3.99984385397,
                Q=0.7071752369554193,
                fc=1681.9744509555319,
                rate=self.rate,
                filter_type='high_shelf_DeMan'
            ),
            'high_pass': IIRFilter(
                G=0.0,
                Q=0.5003270373253953,
                fc=38.13547087613982,
                rate=self.rate,
                filter_type='high_pass_DeMan'
            )
        }

    def integrated_loudness(self, data):
        """计算集成响度"""
        # 验证输入数据
        self._validate_audio(data)

        # 处理单声道输入
        if data.ndim == 1:
            data = data.reshape(-1, 1)

        num_channels = data.shape[1]
        num_samples = data.shape[0]

        # 声道增益 (左/右/中:1.0, 环绕:1.41)
        G = [1.0] * num_channels
        if num_channels >= 4:
            G[3] = 1.41  # 左环绕
        if num_channels >= 5:
            G[4] = 1.41  # 右环绕

        # 应用K加权滤波器
        filtered_data = data.copy()
        for ch in range(num_channels):
            for filter_name in ['high_pass', 'high_shelf']:
                filtered_data[:, ch] = self.filters[filter_name].apply_filter(filtered_data[:, ch])

        # 分块处理参数
        samples_per_block = int(self.block_size * self.rate)
        step = int(samples_per_block * 0.25)  # 75%重叠
        num_blocks = (num_samples - samples_per_block) // step + 1

        # 计算每个块的均方值
        z = np.zeros((num_channels, num_blocks))
        for ch in range(num_channels):
            for j in range(num_blocks):
                start = j * step
                end = start + samples_per_block
                block = filtered_data[start:end, ch]
                z[ch, j] = np.mean(block ** 2)

        # 计算每个块的响度
        with warnings.catch_warnings():
            warnings.simplefilter("ignore", category=RuntimeWarning)
            l = np.array([
                -0.691 + 10 * np.log10(np.sum([G[i] * z[i, j] for i in range(num_channels)]))
                for j in range(num_blocks)
            ])

        # 绝对门限处理 (-70 LUFS)
        absolute_threshold = -70
        above_absolute = l >= absolute_threshold

        if not np.any(above_absolute):
            return -np.inf

        # 计算相对门限
        z_avg_abs = np.array([
            np.mean(z[i, above_absolute]) for i in range(num_channels)
        ])
        relative_threshold = -0.691 + 10 * np.log10(
            np.sum([G[i] * z_avg_abs[i] for i in range(num_channels)])
        ) - 10

        # 应用相对门限
        above_both = (l > relative_threshold) & (l > absolute_threshold)

        if not np.any(above_both):
            return -np.inf

        # 计算最终响度
        z_avg_final = np.array([
            np.mean(z[i, above_both]) for i in range(num_channels)
        ])
        LUFS = -0.691 + 10 * np.log10(
            np.sum([G[i] * z_avg_final[i] for i in range(num_channels)])
        )

        return LUFS

    def _validate_audio(self, data):
        """验证音频数据"""
        if not isinstance(data, np.ndarray):
            raise ValueError("输入必须是numpy数组")

        if not np.issubdtype(data.dtype, np.floating):
            raise ValueError("输入必须是浮点类型")

        if data.ndim == 2 and data.shape[1] > 5:
            raise ValueError("音频最多支持5个声道")

        if data.shape[0] < self.block_size * self.rate:
            raise ValueError("音频长度必须大于块大小")


# 读取音频文件
audio_path = "test.wav"
data, samplerate = sf.read(audio_path)

# 确保音频数据是浮点数格式
if data.dtype != np.float32 and data.dtype != np.float64:
    if data.dtype == np.int16:
        data = data.astype(np.float32) / 32768.0
    elif data.dtype == np.int32:
        data = data.astype(np.float32) / 2147483648.0
    elif data.dtype == np.uint8:
        data = (data.astype(np.float32) - 128) / 128.0
    else:
        data = data.astype(np.float32)
        max_val = np.max(np.abs(data))
        if max_val > 1.0:
            data = data / max_val

# 创建响度计
meter = LoudnessMeter(rate=samplerate)

# 计算集成响度
loudness = meter.integrated_loudness(data)
print(f"LUFS响度: {loudness:.2f} LUFS")

四、与Audition一致性验证

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

五、行业标准参考值

不同平台和场景有不同的 LUFS 目标值:

平台/场景 目标 LUFS 峰值限制
广播 (EBU R128) -23.0 -1.0 dBTP
流媒体 (Spotify) -14.0 -1.0 dBTP
流媒体 (YouTube) -14.0 -1.0 dBTP
流媒体 (Apple Music) -16.0 -1.0 dBTP
播客 -16.0 至 -20.0 -1.0 dBTP
  • 目标 LUFS:表示不同平台或场景下推荐的响度水平。
  • 峰值限制:表示音频信号的最大峰值限制,通常以 dBTP(dB True Peak)表示。
Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐