pytorch深度学习入门(6)之-Torchaudio音频采样

音频重采样

音频重采样是指将一个音频信号的采样率、声道数和采样位数转换到另一个格式的过程。这个过程通常涉及到一个或多个转换步骤,每个步骤都可能改变音频信号的属性。

重采样的原因可能是由于从网络流、本地媒体文件等各种渠道解码的AVFrame帧,其采样位数、声道数、采样率都是不确定的,但是在很多的播放器框架中,需要播放指定的采样位数、声道数、采样率的音频数据,因此需要首先进行格式转换。

在重采样过程中,可以根据需要选择不同的采样率、声道数和采样位数。例如,可以将单声道音频转换为立体声音频,或者将高采样率的音频转换为低采样率的音频。

重采样可以通过多种方式实现,包括使用开源代码库(如FFmpeg、Speex等)或自行编写代码实现。在进行重采样时,需要注意确保音频信号不会出现失真或者噪声,并且要确保转换后的音频信号与原始音频信号的音质保持一致。
本教程展示如何使用 torchaudio 的重采样 API。

import torch
import torchaudio
import torchaudio.functional as F
import torchaudio.transforms as T

print(torch.__version__)
print(torchaudio.__version__)

输出:

2.1.1
2.1.0

首先,我们导入模块并定义辅助函数。

import math
import timeit

import librosa
import matplotlib.colors as mcolors
import matplotlib.pyplot as plt
import pandas as pd
import resampy
from IPython.display import Audio

pd.set_option("display.max_rows", None)
pd.set_option("display.max_columns", None)

DEFAULT_OFFSET = 201


def _get_log_freq(sample_rate, max_sweep_rate, offset):
    """Get freqs evenly spaced out in log-scale, between [0, max_sweep_rate // 2]

    offset is used to avoid negative infinity `log(offset + x)`.

    """
    start, stop = math.log(offset), math.log(offset + max_sweep_rate // 2)
    return torch.exp(torch.linspace(start, stop, sample_rate, dtype=torch.double)) - offset


def _get_inverse_log_freq(freq, sample_rate, offset):
    """Find the time where the given frequency is given by _get_log_freq"""
    half = sample_rate // 2
    return sample_rate * (math.log(1 + freq / offset) / math.log(1 + half / offset))


def _get_freq_ticks(sample_rate, offset, f_max):
    # Given the original sample rate used for generating the sweep,
    # find the x-axis value where the log-scale major frequency values fall in
    times, freq = [], []
    for exp in range(2, 5):
        for v in range(1, 10):
            f = v * 10**exp
            if f < sample_rate // 2:
                t = _get_inverse_log_freq(f, sample_rate, offset) / sample_rate
                times.append(t)
                freq.append(f)
    t_max = _get_inverse_log_freq(f_max, sample_rate, offset) / sample_rate
    times.append(t_max)
    freq.append(f_max)
    return times, freq


def get_sine_sweep(sample_rate, offset=DEFAULT_OFFSET):
    max_sweep_rate = sample_rate
    freq = _get_log_freq(sample_rate, max_sweep_rate, offset)
    delta = 2 * math.pi * freq / sample_rate
    cummulative = torch.cumsum(delta, dim=0)
    signal = torch.sin(cummulative).unsqueeze(dim=0)
    return signal


def plot_sweep(
    waveform,
    sample_rate,
    title,
    max_sweep_rate=48000,
    offset=DEFAULT_OFFSET,
):
    x_ticks = [100, 500, 1000, 5000, 10000, 20000, max_sweep_rate // 2]
    y_ticks = [1000, 5000, 10000, 20000, sample_rate // 2]

    time, freq = _get_freq_ticks(max_sweep_rate, offset, sample_rate // 2)
    freq_x = [f if f in x_ticks and f <= max_sweep_rate // 2 else None for f in freq]
    freq_y = [f for f in freq if f in y_ticks and 1000 <= f <= sample_rate // 2]

    figure, axis = plt.subplots(1, 1)
    _, _, _, cax = axis.specgram(waveform[0].numpy(), Fs=sample_rate)
    plt.xticks(time, freq_x)
    plt.yticks(freq_y, freq_y)
    axis.set_xlabel("Original Signal Frequency (Hz, log scale)")
    axis.set_ylabel("Waveform Frequency (Hz)")
    axis.xaxis.grid(True, alpha=0.67)
    axis.yaxis.grid(True, alpha=0.67)
    figure.suptitle(f"{title} (sample rate: {sample_rate} Hz)")
    plt.colorbar(cax)

要将音频波形从一种频率重新采样为另一种频率,您可以使用 torchaudio.transforms.Resample或 torchaudio.functional.resample()。 transforms.Resample预先计算并缓存用于重采样的内核,同时functional.resample进行动态计算,因此 torchaudio.transforms.Resample在使用相同参数对多个波形进行重采样时,使用将导致加速

两种重采样方法都使用带限正弦插值来计算任意时间步长的信号值。实现涉及卷积,因此我们可以利用GPU/多线程来提高性能。

注意:

=== 在多个子进程中使用重采样时(例如使用多个工作进程加载数据),您的应用程序可能会创建比系统能够有效处理的线程更多的线程。在这种情况下,设置torch.set_num_threads(1)可能会有所帮助。===

由于有限数量的样本只能代表有限数量的频率,因此重采样不会产生完美的结果,并且可以使用多种参数来控制其质量和计算速度。我们通过对对数正弦扫描重新采样来演示这些特性,这是一种频率随时间呈指数增长的正弦波。

下面的频谱图显示了信号的频率表示,其中 x 轴对应于原始波形的频率(以对数刻度表示),y 轴对应于绘制波形的频率,颜色强度对应于幅度。

sample_rate = 48000
waveform = get_sine_sweep(sample_rate)

plot_sweep(waveform, sample_rate, title="Original Waveform")
Audio(waveform.numpy()[0], rate=sample_rate)

原始波形(采样率:48000 Hz)
在这里插入图片描述

现在我们对其进行重新采样(下采样)。

我们看到,在重采样波形的频谱图中,存在原始波形中不存在的伪影。这种效应称为混叠。 本页解释了它是如何发生的,以及为什么它看起来像反射。

resample_rate = 32000
resampler = T.Resample(sample_rate, resample_rate, dtype=waveform.dtype)
resampled_waveform = resampler(waveform)

plot_sweep(resampled_waveform, resample_rate, title="Resampled Waveform")
Audio(resampled_waveform.numpy()[0], rate=resample_rate)

重采样波形(采样率:32000 Hz)
在这里插入图片描述

使用参数控制重采样质量
低通滤波器宽度
由于用于插值的滤波器无限延伸,因此该 lowpass_filter_width参数用于控制用于对插值进行加窗的滤波器的宽度。它也称为过零数,因为插值在每个时间单位都经过零。使用较大的滤波器lowpass_filter_width 可提供更清晰、更精确的滤波器,但计算成本更高。

sample_rate = 48000
resample_rate = 32000

resampled_waveform = F.resample(waveform, sample_rate, resample_rate, lowpass_filter_width=6)
plot_sweep(resampled_waveform, resample_rate, title="lowpass_filter_width=6")

lowpass_filter_width=6(采样率:32000 Hz)
在这里插入图片描述

resampled_waveform = F.resample(waveform, sample_rate, resample_rate, lowpass_filter_width=128)
plot_sweep(resampled_waveform, resample_rate, title="lowpass_filter_width=128")

在这里插入图片描述

Rolloff
该rolloff参数表示为奈奎斯特频率的分数,奈奎斯特频率是给定有限采样率可表示的最大频率。rolloff确定低通滤波器截止并控制混叠程度,当高于奈奎斯特的频率映射到较低频率时会发生混叠。因此,较低的滚降将减少混叠量,但也会减少一些较高的频率。

sample_rate = 48000
resample_rate = 32000
resampled_waveform = F.resample(waveform, sample_rate, resample_rate, rolloff=0.99)
plot_sweep(resampled_waveform, resample_rate, title="rolloff=0.99")

在这里插入图片描述

resampled_waveform = F.resample(waveform, sample_rate, resample_rate, rolloff=0.8)
plot_sweep(resampled_waveform, resample_rate, title="rolloff=0.8")

滚降=0.8(采样率:32000 Hz)

窗函数
默认情况下,torchaudio的重采样使用 Hann 窗滤波器,它是一个加权余弦函数。它还支持kaiser窗,这是一种接近最佳的窗函数,其中包含一个附加 beta参数,允许设计滤波器的平滑度和脉冲宽度。这可以使用参数来控制 resampling_method。

sample_rate = 48000
resample_rate = 32000

resampled_waveform = F.resample(waveform, sample_rate, resample_rate, resampling_method="sinc_interp_hann")
plot_sweep(resampled_waveform, resample_rate, title="Hann Window Default")

Hann 窗口默认(采样率:32000 Hz)

resampled_waveform = F.resample(waveform, sample_rate, resample_rate, resampling_method="sinc_interp_kaiser")
plot_sweep(resampled_waveform, resample_rate, title="Kaiser Window Default")

Kaiser 窗口默认(采样率:32000 Hz)

与 librosa 的比较
torchaudio的重采样函数可用于产生类似于 librosa (resampy) 的 kaiser 窗口重采样的结果,但有一些噪声

sample_rate = 48000
resample_rate = 32000

kaiser_best

resampled_waveform = F.resample(
    waveform,
    sample_rate,
    resample_rate,
    lowpass_filter_width=64,
    rolloff=0.9475937167399596,
    resampling_method="sinc_interp_kaiser",
    beta=14.769656459379492,
)
plot_sweep(resampled_waveform, resample_rate, title="Kaiser Window Best (torchaudio)")

Kaiser Window Best (torchaudio)(采样率:32000 Hz)

librosa_resampled_waveform = torch.from_numpy(
    librosa.resample(waveform.squeeze().numpy(), orig_sr=sample_rate, target_sr=resample_rate, res_type="kaiser_best")
).unsqueeze(0)
plot_sweep(librosa_resampled_waveform, resample_rate, title="Kaiser Window Best (librosa)")

Kaiser Window Best (librosa)(采样率:32000 Hz)

mse = torch.square(resampled_waveform - librosa_resampled_waveform).mean().item()
print("torchaudio and librosa kaiser best MSE:", mse)

输出:

torchaudio and librosa kaiser best MSE: 2.0806901153660115e-06

kaiser_fast
resampled_waveform = F.resample(
waveform,
sample_rate,
resample_rate,
lowpass_filter_width=16,
rolloff=0.85,
resampling_method=“sinc_interp_kaiser”,
beta=8.555504641634386,
)
plot_sweep(resampled_waveform, resample_rate, title=“Kaiser Window Fast (torchaudio)”)
在这里插入图片描述

librosa_resampled_waveform = torch.from_numpy(
    librosa.resample(waveform.squeeze().numpy(), orig_sr=sample_rate, target_sr=resample_rate, res_type="kaiser_fast")
).unsqueeze(0)
plot_sweep(librosa_resampled_waveform, resample_rate, title="Kaiser Window Fast (librosa)")

Kaiser Window Fast (librosa)(采样率:32000 Hz)

mse = torch.square(resampled_waveform - librosa_resampled_waveform).mean().item()
print("torchaudio and librosa kaiser fast MSE:", mse)

输出:

torchaudio and librosa kaiser fast MSE: 2.5200744248601437e-05

性能基准测试
以下是两对采样率之间的下采样和上采样波形的基准。lowpass_filter_width我们演示了窗口类型和采样率可能产生的性能影响。librosa此外,我们还提供了与的 比较kaiser_best并kaiser_fast使用它们在 中的相应参数torchaudio。

print(f"torchaudio: {torchaudio.__version__}")
print(f"librosa: {librosa.__version__}")
print(f"resampy: {resampy.__version__}")

输出:

torchaudio: 2.1.0
librosa: 0.10.0
resampy: 0.2.2
def benchmark_resample_functional(
    waveform,
    sample_rate,
    resample_rate,
    lowpass_filter_width=6,
    rolloff=0.99,
    resampling_method="sinc_interp_hann",
    beta=None,
    iters=5,
):
    return (
        timeit.timeit(
            stmt="""
torchaudio.functional.resample(
    waveform,
    sample_rate,
    resample_rate,
    lowpass_filter_width=lowpass_filter_width,
    rolloff=rolloff,
    resampling_method=resampling_method,
    beta=beta,
)
        """,
            setup="import torchaudio",
            number=iters,
            globals=locals(),
        )
        * 1000
        / iters
    )
def benchmark_resample_transforms(
    waveform,
    sample_rate,
    resample_rate,
    lowpass_filter_width=6,
    rolloff=0.99,
    resampling_method="sinc_interp_hann",
    beta=None,
    iters=5,
):
    return (
        timeit.timeit(
            stmt="resampler(waveform)",
            setup="""
import torchaudio

resampler = torchaudio.transforms.Resample(
    sample_rate,
    resample_rate,
    lowpass_filter_width=lowpass_filter_width,
    rolloff=rolloff,
    resampling_method=resampling_method,
    dtype=waveform.dtype,
    beta=beta,
)
resampler.to(waveform.device)
        """,
            number=iters,
            globals=locals(),
        )
        * 1000
        / iters
    )
def benchmark_resample_librosa(
    waveform,
    sample_rate,
    resample_rate,
    res_type=None,
    iters=5,
):
    waveform_np = waveform.squeeze().numpy()
    return (
        timeit.timeit(
            stmt="""
librosa.resample(
    waveform_np,
    orig_sr=sample_rate,
    target_sr=resample_rate,
    res_type=res_type,
)
        """,
            setup="import librosa",
            number=iters,
            globals=locals(),
        )
        * 1000
        / iters
    )
def benchmark(sample_rate, resample_rate):
    times, rows = [], []
    waveform = get_sine_sweep(sample_rate).to(torch.float32)

    args = (waveform, sample_rate, resample_rate)

    # sinc 64 zero-crossings
    f_time = benchmark_resample_functional(*args, lowpass_filter_width=64)
    t_time = benchmark_resample_transforms(*args, lowpass_filter_width=64)
    times.append([None, f_time, t_time])
    rows.append("sinc (width 64)")

    # sinc 6 zero-crossings
    f_time = benchmark_resample_functional(*args, lowpass_filter_width=16)
    t_time = benchmark_resample_transforms(*args, lowpass_filter_width=16)
    times.append([None, f_time, t_time])
    rows.append("sinc (width 16)")

    # kaiser best
    kwargs = {
        "lowpass_filter_width": 64,
        "rolloff": 0.9475937167399596,
        "resampling_method": "sinc_interp_kaiser",
        "beta": 14.769656459379492,
    }
    lib_time = benchmark_resample_librosa(*args, res_type="kaiser_best")
    f_time = benchmark_resample_functional(*args, **kwargs)
    t_time = benchmark_resample_transforms(*args, **kwargs)
    times.append([lib_time, f_time, t_time])
    rows.append("kaiser_best")

    # kaiser fast
    kwargs = {
        "lowpass_filter_width": 16,
        "rolloff": 0.85,
        "resampling_method": "sinc_interp_kaiser",
        "beta": 8.555504641634386,
    }
    lib_time = benchmark_resample_librosa(*args, res_type="kaiser_fast")
    f_time = benchmark_resample_functional(*args, **kwargs)
    t_time = benchmark_resample_transforms(*args, **kwargs)
    times.append([lib_time, f_time, t_time])
    rows.append("kaiser_fast")

    df = pd.DataFrame(times, columns=["librosa", "functional", "transforms"], index=rows)
    return df
def plot(df):
    print(df.round(2))
    ax = df.plot(kind="bar")
    plt.ylabel("Time Elapsed [ms]")
    plt.xticks(rotation=0, fontsize=10)
    for cont, col, color in zip(ax.containers, df.columns, mcolors.TABLEAU_COLORS):
        label = ["N/A" if v != v else str(v) for v in df[col].round(2)]
        ax.bar_label(cont, labels=label, color=color, fontweight="bold", fontsize="x-small")

下采样(48 -> 44.1 kHz)

df = benchmark(48_000, 44_100)
plot(df)

在这里插入图片描述输出:

                 librosa  functional  transforms
sinc (width 64)      NaN        0.87        0.38
sinc (width 16)      NaN        0.76        0.35
kaiser_best        81.05        1.28        0.37
kaiser_fast         7.88        0.93        0.34

下采样(16 -> 8 kHz)

df = benchmark(16_000, 8_000)
plot(df)

在这里插入图片描述

输出:

                 librosa  functional  transforms
sinc (width 64)      NaN        1.28        1.09
sinc (width 16)      NaN        0.52        0.37
kaiser_best        11.25        1.38        1.16
kaiser_fast         3.12        0.58        0.39

上采样(44.1 -> 48 kHz)
df = benchmark(44_100, 48_000)
plot(df)
音频重采样教程

                 librosa  functional  transforms
sinc (width 64)      NaN        0.85        0.37
sinc (width 16)      NaN        0.68        0.34
kaiser_best        32.63        1.10        0.37
kaiser_fast         7.82        0.91        0.33

上采样(8 -> 16 kHz)

df = benchmark(8_000, 16_000)
plot(df)

音频重采样教程

                 librosa  functional  transforms
sinc (width 64)      NaN        0.66        0.48
sinc (width 16)      NaN        0.36        0.21
kaiser_best        11.19        0.70        0.48
kaiser_fast         2.95        0.40        0.23

概括
详细说明结果:

较大lowpass_filter_width会导致较大的重采样内核,因此会增加内核计算和卷积的计算时间

使用sinc_interp_kaiser比默认值更长的计算时间, sinc_interp_hann因为计算中间窗口值更复杂

采样率和重采样率之间的大 GCD 将导致简化,从而允许更小的内核和更快的内核计算。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码农呆呆

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值