用Python和Librosa库,5分钟搞定音频频率分析(附完整代码和音高对照表)
用Python和Librosa库快速实现音频频率分析与音高识别实战指南
你是否曾经好奇一段旋律中隐藏着怎样的频率秘密?当吉他手弹奏出一个和弦时,如何准确知道每个音符对应的音高?在音乐制作、语音分析甚至乐器调音等场景中,快速准确地分析音频频率并映射到具体音高是一项极具实用价值的技能。本文将带你用Python生态中最强大的音频处理库Librosa,在短短几分钟内完成从音频文件到音高识别的完整流程。
1. 环境准备与Librosa库安装
工欲善其事,必先利其器。我们需要先搭建好Python环境并安装必要的库。Librosa是一个专门为音乐和音频分析设计的Python包,它提供了丰富的信号处理功能和简洁的API接口。
首先确保你已经安装了Python 3.6或更高版本。然后通过pip安装Librosa及其依赖库:
pip install librosa numpy matplotlib注意:Librosa在处理音频文件时依赖ffmpeg,如果遇到相关错误,可能需要额外安装ffmpeg。在Ubuntu上可以通过
sudo apt-get install ffmpeg安装,Mac用户可以使用brew install ffmpeg。
安装完成后,我们可以通过以下代码验证安装是否成功:
import librosa print("Librosa版本:", librosa.__version__)这个强大的音频处理库将为我们提供以下核心功能:
- 音频文件加载与重采样
- 频谱特征提取
- 节拍与节奏分析
- 音高与音色检测
2. 音频文件加载与预处理
实际工作中,我们可能遇到各种格式的音频文件。Librosa支持WAV、MP3等常见格式,能够自动处理采样率和位深度等细节。
让我们从一个简单的WAV文件开始:
import librosa import librosa.display import matplotlib.pyplot as plt # 加载音频文件 audio_path = 'your_audio.wav' y, sr = librosa.load(audio_path, sr=None) # sr=None保持原始采样率 print(f"音频时长: {len(y)/sr:.2f}秒") print(f"采样率: {sr}Hz")音频加载后,我们通常需要进行一些预处理以提高分析准确性。常见的预处理步骤包括:
- 重采样:统一不同音频的采样率
- 归一化:将振幅缩放到统一范围
- 静音去除:消除无声音段
- 分帧处理:将连续音频切分为短时帧
以下是一个完整的预处理示例:
# 统一重采样到22050Hz TARGET_SR = 22050 y_resampled = librosa.resample(y, orig_sr=sr, target_sr=TARGET_SR) # 振幅归一化 y_normalized = librosa.util.normalize(y_resampled) # 可视化原始波形 plt.figure(figsize=(14, 5)) librosa.display.waveshow(y_normalized, sr=TARGET_SR) plt.title('归一化后的音频波形') plt.xlabel('时间(秒)') plt.ylabel('振幅') plt.show()3. 频谱分析与基频提取
要确定音频的主频率,我们需要将时域信号转换到频域。短时傅里叶变换(STFT)是这一过程的核心技术。
Librosa提供了简便的函数来计算频谱:
# 计算短时傅里叶变换 D = librosa.stft(y_normalized) # 将幅度谱转换为dB单位 S_db = librosa.amplitude_to_db(abs(D), ref=np.max) # 可视化频谱图 plt.figure(figsize=(14, 5)) librosa.display.specshow(S_db, sr=TARGET_SR, x_axis='time', y_axis='log') plt.colorbar(format='%+2.0f dB') plt.title('频谱图') plt.show()从频谱中提取基频(Fundamental Frequency)是音高识别的关键步骤。Librosa提供了多种方法来实现这一点:
# 使用Librosa的基频估计功能 f0, voiced_flag, voiced_probs = librosa.pyin(y_normalized, fmin=librosa.note_to_hz('C2'), fmax=librosa.note_to_hz('C7')) # 可视化基频轨迹 times = librosa.times_like(f0) plt.figure(figsize=(14, 5)) plt.plot(times, f0, label='f0', color='cyan', linewidth=2) plt.title('基频轨迹') plt.xlabel('时间(秒)') plt.ylabel('频率(Hz)') plt.legend() plt.show()4. 频率到音高的精确映射
获得基频后,我们需要将其映射到音乐理论中的标准音高。西方音乐将音高分为12平均律,每个八度包含12个半音。
以下是一个完整的频率到音高转换函数:
import numpy as np def freq_to_note(freq): """将频率转换为最接近的音符名称和MIDI音高值""" if freq <= 0: return None, None # A4(440Hz)的MIDI音高值为69 semitone = 12 * np.log2(freq / 440.0) + 69 semitone_rounded = int(round(semitone)) # MIDI音高值范围是0-127 if semitone_rounded < 0 or semitone_rounded > 127: return None, None # 音符名称映射 note_names = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'] octave = semitone_rounded // 12 - 1 note_index = semitone_rounded % 12 note_name = f"{note_names[note_index]}{octave}" return note_name, semitone_rounded # 示例:将提取的基频转换为音符 for frequency in f0: if frequency > 0: # 过滤无效值 note, midi_num = freq_to_note(frequency) if note: print(f"频率{frequency:.2f}Hz -> 音符{note} (MIDI:{midi_num})")为了更方便地使用,我们可以创建一个完整的音高对照表:
| MIDI编号 | 音符名称 | 频率(Hz) | MIDI编号 | 音符名称 | 频率(Hz) |
|---|---|---|---|---|---|
| 60 | C4 | 261.63 | 72 | C5 | 523.25 |
| 61 | C#4 | 277.18 | 73 | C#5 | 554.37 |
| 62 | D4 | 293.66 | 74 | D5 | 587.33 |
| 63 | D#4 | 311.13 | 75 | D#5 | 622.25 |
| 64 | E4 | 329.63 | 76 | E5 | 659.26 |
| 65 | F4 | 349.23 | 77 | F5 | 698.46 |
| 66 | F#4 | 369.99 | 78 | F#5 | 739.99 |
| 67 | G4 | 392.00 | 79 | G5 | 783.99 |
| 68 | G#4 | 415.30 | 80 | G#5 | 830.61 |
| 69 | A4 | 440.00 | 81 | A5 | 880.00 |
| 70 | A#4 | 466.16 | 82 | A#5 | 932.33 |
| 71 | B4 | 493.88 | 83 | B5 | 987.77 |
5. 完整工作流与实战案例
现在,我们将所有步骤整合成一个完整的音频分析工作流。以下代码实现了从音频文件加载到音高识别的全过程:
def analyze_audio_pitch(audio_path): """完整的音频频率分析和音高识别流程""" # 1. 加载音频 y, sr = librosa.load(audio_path, sr=None) y_resampled = librosa.resample(y, orig_sr=sr, target_sr=22050) y_normalized = librosa.util.normalize(y_resampled) # 2. 提取基频 f0, voiced_flag, voiced_probs = librosa.pyin(y_normalized, fmin=librosa.note_to_hz('C2'), fmax=librosa.note_to_hz('C7')) # 3. 分析结果 pitch_results = [] for time, freq in zip(librosa.times_like(f0), f0): if freq > 0: # 有效频率 note, midi = freq_to_note(freq) if note: pitch_results.append({ 'time': time, 'frequency': freq, 'note': note, 'midi': midi }) return pitch_results # 使用示例 results = analyze_audio_pitch('sample.wav') for r in results[:10]: # 打印前10个结果 print(f"时间{r['time']:.2f}s: {r['frequency']:.2f}Hz -> {r['note']} (MIDI:{r['midi']})")在实际应用中,我们可能会遇到一些挑战和特殊情况:
- 和声分析:当音频包含多个同时发声的音符时,简单的基频提取可能不够
- 噪声干扰:环境噪声会影响频率检测的准确性
- 动态音高:滑音或颤音会导致频率持续变化
针对这些情况,我们可以采用更高级的技术:
# 使用谐波乘积谱提高和声分析能力 y_harmonic = librosa.effects.harmonic(y_normalized) f0_harmonic = librosa.yin(y_harmonic, fmin=librosa.note_to_hz('C2'), fmax=librosa.note_to_hz('C7')) # 使用动态时间规整(DTW)分析音高变化轨迹 D = librosa.stft(y_normalized) times = librosa.times_like(D) frequencies = librosa.fft_frequencies(sr=TARGET_SR)通过本教程介绍的方法,你已经掌握了使用Python和Librosa库进行音频频率分析和音高识别的基本技能。这套工具在音乐信息检索(MIR)、语音处理、乐器调音等领域都有广泛应用。
