方法:librosa.effects.trim 说明

作用
librosa.effects.trim 用于 音频的静音裁剪,会从音频信号的两端去除低于一定响度阈值的静音部分。

使用案例:

1
2
3
4
5
librosa.effects.trim(
speech, top_db=top_db,
frame_length=win_length,
hop_length=hop_length
)

参数解析
speech:

输入的音频信号,通常是一个 1D NumPy 数组,表示单声道的音频波形。
top_db:

静音阈值,单位是 dB。
    声音能量低于最大值减去 top_db 的部分将被认为是静音并裁剪。
    示例:如果音频的最大响度为 -10 dB,top_db=60 会裁剪掉小于 -70 dB 的部分。
frame_length:

用于计算能量的帧长度,单位是样本数。
    更大的值会使裁剪基于更大的音频片段,适合平滑的裁剪。
hop_length:

帧之间的步长,单位是样本数。
    决定了分析窗口移动的粒度,较小的值会更精确,但可能更慢。
    返回值
    trimmed_audio:

裁剪后的音频信号,NumPy 数组格式。
index:

裁剪保留部分的索引范围 (start, end),单位为样本数。
    可用于后续音频操作,比如对齐字幕。

示例代码
以下示例演示如何裁剪静音:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import librosa
import numpy as np

# 加载音频信号 (模拟数据)
speech = np.random.randn(16000 * 5) # 生成 5 秒的伪音频信号
speech[:16000] *= 0.01 # 前 1 秒为低能量的“静音”

# 裁剪静音部分
trimmed_audio, index = librosa.effects.trim(
speech, top_db=40, frame_length=1024, hop_length=512
)

print(f"Original length: {len(speech)} samples")
print(f"Trimmed length: {len(trimmed_audio)} samples")
print(f"Kept range: {index}")

工作原理

  • 计算每帧能量 :
  • 音频会被分割成多个小帧(由 frame_length 决定)。
  • 每帧的能量计算公式:
    Energy(frame)=∑(Amplitude2)\text{Energy(frame)} = \sum (\text{Amplitude}^2)Energy(frame)=(Amplitude2**)**
  • 比较能量与阈值 :
  • 每帧能量会与最大能量减去 top_db 的值进行比较。
  • 超过阈值的帧被保留,其余帧视为静音。
  • 裁剪静音部分 :
  • 根据帧的起始和结束位置,裁剪音频两端的静音部分。

使用场景

  1. 去除录音中的前后静音 :
  • 实际录音中,常见静音部分可能是等待或结束时的无声段。
  1. 节省存储空间 :
  • 裁剪掉无用的静音部分减少音频长度。
  1. 提高处理效率 :
  • 预处理后音频长度更短,节省后续处理的计算量。

方法:waveform.mean(dim=0, keepdim=True)

waveform.mean(dim=0, keepdim=True) 是 PyTorch 张量操作的一种用法,具体作用取决于 waveform 的形状。以下是详细解释:

语法说明

  • mean(dim=0) : 在指定的维度 dim=0 上计算均值。
  • keepdim=True : 保留原始维度。即使均值降低了张量的维度,也会通过填充维度大小为 1 来保持张量结构。

适用场景

通常,waveform 是一个音频信号张量,形状为 (channels, samples),表示音频的通道数和采样点数。

  1. 单通道(单声道)音频 :
    如果 waveform 是单通道音频 (shape: (samples,)),dim=0 不适用,因为它没有第 0 维。
  2. 多通道(如立体声)音频 :
    如果 waveform 是多通道音频 (shape: (channels, samples)),dim=0 表示在通道维度上操作。例如,对于立体声,channels=2,那么在 dim=0 上取均值会将多个通道的音频合成为单通道。

操作示例

1. 多通道音频

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import torch

# 两个通道的音频信号
waveform = torch.tensor([
[1.0, 2.0, 3.0], # 通道 1
[4.0, 5.0, 6.0] # 通道 2
]) # shape: (2, 3)

# 在通道维度上取均值
result = waveform.mean(dim=0, keepdim=True)

print(result)
# 输出: tensor([[2.5, 3.5, 4.5]]) # 平均每个采样点的两个通道信号
print(result.shape)
# 输出: torch.Size([1, 3]) # 保留了第一维度

2. 单通道音频

1
2
3
4
waveform = torch.tensor([1.0, 2.0, 3.0])  # shape: (3,)

# 直接在维度 0 上取均值(不适用)
# waveform.mean(dim=0) # 报错,因为只有一个维度

3. keepdim=False 的效果

如果 keepdim=False,结果张量的维度会减少:

1
2
3
result = waveform.mean(dim=0, keepdim=False)
print(result.shape) # torch.Size([3]),减少了一个维度

总结

waveform.mean(dim=0, keepdim=True) 的核心作用是:

  1. 在通道维度上取均值 ,将多通道音频合并为单通道。
  2. 保留原始维度 ,避免因维度缩减而导致后续操作不一致。

适合用于将多通道音频(如立体声)转换为单通道音频,同时保持张量形状的一致性。

waveform.mean 音频时长变为 0 问题分析:

waveform.mean(dim=0, keepdim=True) 之后,speech 的音频时长变为 0,可能是由于以下原因:

1
2
3
waveform = prompt_wav['waveform'].squeeze(0)
source_sr = prompt_wav['sample_rate']
speech = waveform.mean(dim=0, keepdim=True)

之后,speech 的音频时长变为 0,可能是由于以下原因:


1. 检查 waveform 数据是否正确

  • 输入 waveform 是否有有效数据:确认 prompt_wav['waveform'] 的内容不是全为零或噪音。
  • squeeze(0) 的影响waveform 是否从 (1, N) 转成 (N,)

验证方法:
squeeze(0).mean(dim=0, keepdim=True) 执行前后打印 waveform 的维度和数据。

1
2
print(f"Before squeeze: {prompt_wav['waveform'].shape}")
print(f"After squeeze: {waveform.shape}")

2. 检查 speech = waveform.mean(dim=0, keepdim=True) 的结果

mean(dim=0, keepdim=True) 会对张量的第 0 维进行均值计算。如果 waveform 是单声道 (即 waveform.shape == (N,)),mean(dim=0) 会将其降维为一个标量,结果的形状变成 (1,),这导致保存音频时产生 0 时长。

解决方法:
如果音频是单声道,不需要取均值。直接跳过 mean 操作:

1
2
3
4
if waveform.dim() == 1:  # 单声道
speech = waveform.unsqueeze(0) # 保持 (1, N)
else:
speech = waveform.mean(dim=0, keepdim=True) # 多声道取平均

3. 保存音频时的形状问题

音频保存方法需要 (1, N) 形状的张量(1 表示通道数,N 表示样本数)。如果 speech 的形状是 (1,),会被认为是无效数据。

验证方法:
打印 speech.shapespeech 的数据内容:

1
2
print(f"Speech shape: {speech.shape}")
print(f"Speech data: {speech}")

综合解决方法

  1. 确认 waveform 数据是否正确。
  2. 仅对多声道音频进行均值计算。
  3. 确保 speech 在保存前的形状为 (1, N)

完整代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
waveform = prompt_wav['waveform'].squeeze(0)
source_sr = prompt_wav['sample_rate']

# 检查 waveform 数据
print(f"Waveform shape after squeeze: {waveform.shape}")

# 单声道不取均值,多声道取均值
if waveform.dim() == 1:
speech = waveform.unsqueeze(0) # 保持 (1, N)
else:
speech = waveform.mean(dim=0, keepdim=True)

print(f"Speech shape before save: {speech.shape}")

# 确保保存前的采样率和张量格式
torchaudio.save("test/output.wav", speech, source_sr)

检查输出:

  • 文件大小是否正常。
  • 播放生成的音频是否包含有效内容。

如果问题依然存在

  1. 检查输入 prompt_wav 是否被其他逻辑修改过。
  2. 如果数据被裁剪(如 librosa.effects.trim),需要确认裁剪后数据的有效性。

获取 wavform 时长,样本数

是的,waveform 可以间接用于获取音频时长,前提是知道音频的 采样率 (sample_rate)。


计算音频时长公式

音频时长可以通过以下公式计算:
[
\text{时长 (秒)} = \frac{\text{样本数}}{\text{采样率 (Hz)}}
]

相关参数

  • 样本数: waveform 的形状决定样本数,具体为 waveform.shape[-1](最后一维表示采样点的数量)。
  • 采样率 (sample_rate): 每秒钟音频采样的点数(以 Hz 为单位)。例如,常见值有 16000 Hz, 44100 Hz 等。

单通道音频

如果音频是单声道,waveform 形状是 (samples,)(1, samples)

示例:

1
2
3
4
5
6
7
8
9
10
import torch

# 单声道音频示例
waveform = torch.randn(16000) # 假设 1 秒的单声道音频,采样率 16,000 Hz
sample_rate = 16000 # 采样率

# 获取样本数和时长
num_samples = waveform.shape[0] # 样本数
duration = num_samples / sample_rate # 时长
print(f"样本数: {num_samples}, 时长: {duration:.2f} 秒")

输出:

1
样本数: 16000, 时长: 1.00 秒

多通道音频

如果音频是多声道,waveform 形状是 (channels, samples),样本数为 waveform.shape[1]

示例:

1
2
3
4
5
6
7
8
# 双声道音频示例
waveform = torch.randn(2, 32000) # 假设 2 秒的双声道音频,采样率 16,000 Hz
sample_rate = 16000

# 获取样本数和时长
num_samples = waveform.shape[1] # 样本数
duration = num_samples / sample_rate # 时长
print(f"样本数: {num_samples}, 时长: {duration:.2f} 秒")

输出:

1
样本数: 32000, 时长: 2.00 秒

Librosa 获取音频时长

如果使用 librosa 加载音频,可以直接获取时长:

1
2
3
4
5
6
7
8
9
import librosa

# 加载音频
audio_path = 'path/to/audio.wav'
waveform, sample_rate = librosa.load(audio_path, sr=None) # sr=None 保持原始采样率

# 直接获取时长
duration = librosa.get_duration(y=waveform, sr=sample_rate)
print(f"时长: {duration:.2f} 秒")

总结

  1. waveformsample_rate 一起可以计算音频时长。
  2. 单声道音频:时长取决于 waveform.shape[0]
  3. 多声道音频:时长取决于 waveform.shape[1]
  4. 如果需要更高层次的封装,可使用 librosa.get_duration 等方法直接计算时长。

警告信息: FutureWarning: You are using torch.load with weights_only=False

信息内容如下:

1
2
FutureWarning: You are using torch.load with weights_only=False (the current default value), which uses the default pickle module implicitly. It is possible to construct malicious pickle data which will execute arbitrary code during unpickling (See https://github.com/pytorch/pytorch/blob/main/SECURITY.md#untrusted-models for more details). In a future release, the default value for weights_only will be flipped to True. This limits the functions that could be executed during unpickling. Arbitrary objects will no longer be allowed to be loaded via this mode unless they are explicitly allowlisted by the user via torch.serialization.add_safe_globals. We recommend you start setting weights_only=True for any use case where you don't have full control of the loaded file. Please open an issue on GitHub for any issues related to this experimental feature.
self.hift.load_state_dict(torch.load(hift_model, map_location=self.device))

这个警告是因为在使用 torch.load 加载模型时,默认的 weights_only 参数被设置为 False,允许加载过程中执行非权重对象的反序列化操作。这可能带来潜在的安全隐患,因为恶意构造的 pickle 数据可能会在加载过程中执行任意代码。

未来版本的 PyTorch 将默认改为 weights_only=True,以限制加载过程中反序列化的内容。您可以通过明确设置 weights_only=True 来解决此警告。

以下是如何修改代码以避免警告的示例:


改进代码

将:

1
self.hift.load_state_dict(torch.load(hift_model, map_location=self.device))

修改为:

1
2
state_dict = torch.load(hift_model, map_location=self.device, weights_only=True)
self.hift.load_state_dict(state_dict)

解释

  1. **weights_only=True**:

    • 此选项告诉 torch.load 只加载权重(state_dict),并跳过与非权重对象相关的反序列化。
    • 它是更安全的选项,因为它避免了潜在的恶意代码执行。
  2. 控制加载对象

    • 如果您的模型文件不仅包含权重,还包含其他对象(如优化器状态等),但您只需要加载权重,可以使用 weights_only=True

对于自定义对象

如果模型文件包含自定义的类对象,且需要在加载时使用,您需要显式声明允许反序列化的对象:

1
2
3
4
5
6
7
8
import torch.serialization

# 添加自定义类到安全全局列表中
torch.serialization.add_safe_globals({"CustomClass": CustomClass})

# 加载模型
state_dict = torch.load(hift_model, map_location=self.device, weights_only=True)
self.hift.load_state_dict(state_dict)

这样可以在安全加载模式下引入特定的自定义类。


未来兼容性

为了与未来的 PyTorch 版本兼容,建议您从现在开始明确指定 weights_only 参数,特别是在加载权重时。例如:

1
state_dict = torch.load(hift_model, map_location=self.device, weights_only=True)

这样可以确保您的代码在 PyTorch 更新后继续正常运行且没有警告。