(多模态系列一)音频算法基础


1、音频基础

什么是声音?声音是介质振动在听觉系统中产生的反应,是一种波,可以用频率、振幅这两个基本物理属性来描述。声音的振幅就是音量,也叫作响度;频率是单位时间振动次数,频率的高低就是指音调,频率用赫兹(Hz)作为单位。

在这里插入图片描述

人耳只能听到20Hz到20kHz范围内的声音,小于20Hz的叫次声波,大于20kHz的叫超声波。超声波在现实中有很多应用,如洗牙、测距、成像等。因为人耳的特性,我们对声音的大小感知呈现对数关系,所以我们通常用分贝描述声音大小。分贝(decibel)是度量两个相同单位之数量比例的单位,主要用于度量声音强度,常用dB表示。声学中,声音的强度定义为声压。计算分贝值时采用20微帕斯卡作为参考值(通常被认为是人类的最少听觉相应值,大约是3米以外飞行的蚊子声音),这一参考值是人类对声音能够感知的阈值下限。

在这里插入图片描述

数字音频(Digital Audio),通过采样和量化技术获得的离散性(数字化)音频数据。计算机内部处理的是二进制数据,所以需要将模拟音频通过采样、量化转换成有限个数字表示的离散序列(即实现音频数字化)。数字音频片段以足够快的速率对模拟波的振幅进行采样,模拟波的固有频率,达到高度接近这种模拟波的效果。量化(Quantization)是将经过采样得到的离散数据转化成二进制数的过程。量化的过程是先将整个幅度划分成有限个小幅度(量化阶距)的集合,把落入某个阶距内的样值归为一类,并赋予相同的量化值。

在这里插入图片描述

采样频率(Sampling Rate),单位时间内采集的样本数,是采样周期的倒数,指两个采样之间的时间间隔。采样频率必须至少是信号中最大频率分量频率的两倍,否则就不能从信号采样中恢复原始信号,这其实就是著名的香农采样定理。例如,要表示人类听觉范围(20-20000Hz)内的音频,数字音频格式必须至少每秒采样40000次(CD音频使用44100Hz的采样率,部分原因也在于此)。CD音质采样率为44.1kHz,其他常用采样率:22.05Hz,11.025Hz,一般网络和移动通信的音频采样率:8kHz。采样频率越高,声音质量越好。一般我们语音通信中(VOIP,例如微信,QQ语音聊天),我们对声音质量要求没那么高,能听清讲的什么即可,所以常采用8kHz采样率。


2、音频可视化与生成

数字音频信息,本质上就是一组沿着时间维度的(波形)一维向量。利用python中的librosa库,可以轻松地读取和处理各种音频文件格式,如WAV、MP3等。

librosa.load()函数用于加载音频文件,其中sr是一个可选参数,代表采样率。这个参数决定了音频数据应该按照多高的速度进行采样。当 sr=None 时,表示不改变原始采样率;而当sr设置为特定的数字如44100等时,意味着需要重新采样到新的采样率。 librosa.load()函数输出的数值都在[-1, 1]范围,因为这些数值实际上都是归一化之后的波形幅度。

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

其中,len(y) / sr 表示音频总时长,其中22050Hz和44100Hz的采样频率,提取出来的波形图是一致的,只是部分信息略有缺失。

import librosa
import librosa.display
import matplotlib.pyplot as plt
import numpy as np
import scipy.io.wavfile as wavfile

audio_path = "003.wav"
y, sr = librosa.load(audio_path)
# print(y.shape): (3914868,)
# print(sr): 22050

# print(len(y) / sr): 177.54503401360543
y= y[:10*sr]    # 可视化10s的音频波形

x = [i for i in range(len(y))]
plt.figure(figsize=(40, 5))
plt.plot(x, y, marker='o')
plt.grid(True)
plt.savefig("1.png")

y, sr = librosa.load(audio_path, sr=44100)
# print(y.shape): (7829735,)
# print(sr): 44100

# print(len(y) / sr): 177.5450113378685
y= y[:10*sr]

x = [i for i in range(len(y))]
plt.figure(figsize=(40, 5))
plt.plot(x, y, marker='o')
plt.grid(True)
plt.savefig("2.png")

也可以使用librosa.display.waveshow函数用于绘制音频的波形图,波形图形状是类似的,只是细节处做的更好。

在这里插入图片描述

import librosa
import librosa.display
import matplotlib.pyplot as plt
import numpy as np
import scipy.io.wavfile as wavfile

audio_file = '003.wav'
y, sr = librosa.load(audio_file)
y= y[:10*sr]

D = librosa.amplitude_to_db(np.abs(librosa.stft(y)), ref=np.max)
plt.figure(figsize=(40, 5))
librosa.display.specshow(D, sr=sr, x_axis='time', y_axis='log')
plt.colorbar(format='%+2.0f dB')
plt.title('Log-frequency power spectrogram')
plt.savefig("4.png")

此外,除了处理现有的音频文件外,还可以自定义用来生成新的音乐,生成音乐通常涉及到使用数学模型或基于规则的方法来合成音频波形。例如,可以使用numpy生成各种音频信号,如正弦波、方波等,也可以通过参数化的方法生成更复杂的波形。

在这里插入图片描述

import librosa
import librosa.display
import matplotlib.pyplot as plt
import numpy as np
import scipy.io.wavfile as wavfile

# 生成正弦波信号
duration = 5  # 时长为5秒
sampling_freq = 44100  # 采样频率
tone_freq = 440  # 音调频率为440Hz(A4音)

t = np.linspace(0, duration, int(sampling_freq * duration), endpoint=False)
audio_data = 0.5 * np.sin(2 * np.pi * tone_freq * t)
# print(audio_data.shape): (220500,)
# print(audio_data): [ 0. 0.03132416  0.06252526 ... -0.09348072 -0.06252526 -0.03132416]
# print(np.min(audio_data), np.mean(audio_data), np.max(audio_data)): -0.4999998731289438 -1.9527841173270782e-17 0.49999987312894395

audio_data_plot = audio_data[:1000]
x = [i for i in range(len(audio_data_plot))]
plt.figure(figsize=(40, 5))
plt.plot(x, audio_data_plot, marker='o')
plt.grid(True)
plt.savefig("5.png")

# 将生成的音频数据保存为WAV文件
wavfile.write('generated_audio.wav', sampling_freq, audio_data.astype(np.float32))

自定义一个时长为5s的正弦波形,采用numpy对其均匀采样,最后导入librosa库生成wav音频文件。

3、音频特征提取

除了读取和播放音频文件、波形可视化和分析,多模态算法中通常需要对一段wav文件提取音频特征,用于实现更复杂的相关任务。

在《Ai choreographer: Music conditioned 3d dance generation with aist++》论文中,给定一个音乐片段,作者使用 Librosa提取音乐2D特征图,维度(L,35),其中 L 是帧数,35 是音乐特征通道,其中包括:1维的envelope、20维的MFCC、12维的chroma、1维的one-hot peaks、1维的one-hot beats。

import os
from functools import partial
from pathlib import Path

import librosa
import librosa as lr
import numpy as np
from tqdm import tqdm
import soundfile as sf


HOP_LENGTH = 512
# SR = FPS * HOP_LENGTH
EPS = 1e-6


def _get_tempo(audio_name):

    """Get tempo (BPM) for a music by parsing music name."""
    # a lot of stuff, only take the 5th element
    audio_name = audio_name.split("_")[4]

    assert len(audio_name) == 4
    if audio_name[0:3] in ["mBR", "mPO", "mLO", "mMH", "mLH", "mWA", "mKR", "mJS", "mJB",]:
        return int(audio_name[3]) * 10 + 80
    elif audio_name[0:3] == "mHO":
        return int(audio_name[3]) * 5 + 110
    else:
        assert False, audio_name


def extract(fpath, skip_completed=True, dest_dir="music_baseline_feats", fps=30):
    # print(fpath): ../sliced_wav/003.wav
    # print(skip_completed): True
    # print(dest_dir): music_baseline_feats
    # print(fps): 30

    audio_name = Path(fpath).stem
    save_path = os.path.join(dest_dir, audio_name + ".npy")
    # print(audio_name): 003
    # print(save_path): music_baseline_feats/003.npy

    """  这里读取音频文件时的采样率真的很低,30*512,相当于一秒内只采样了15360个数据,平均下来一帧采样了512个数据.  """
    SR = fps * HOP_LENGTH
    data, _ = librosa.load(fpath, sr=SR)
    # print(SR): 15360
    # print(data.shape): (524288,)
    # print(len(data) / SR): 34.13333333333333
    # print(np.min(data), np.mean(data), np.max(data)): -1.0525831 -0.0017628843 1.0033232

    """  librosa.onset.onset_strength函数的主要作用是,计算音频信号的起始强度包络。起始强度包络是一个时间序列,表示音频信号中每个时间点
         的起始强度。起始强度通常用于检测音频信号中的音符或鼓点等事件。  """
    envelope = librosa.onset.onset_strength(y=data, sr=SR)  # (seq_len,)
    # print(envelope.shape): (1025,)

    """  使用librosa库中的mfcc函数来计算音频信号的梅尔频率倒谱系数(MFCC,Mel-Frequency Cepstral Coefficients)。
         MFCC是一种广泛用于语音和音频信号处理的特征表示方法,它基于人耳对不同频率的感知特点,将频谱信息转化为一组倒谱系数。
         MFCC常用于语音识别、音频分类等任务.  """
    mfcc = librosa.feature.mfcc(y=data, sr=SR, n_mfcc=20).T  # (seq_len, 20)
    # print(mfcc.shape): (1025, 20)

    """  使用librosa库中的chroma_cens函数来计算音频信号的色度特征(Chroma Features)。
         色度特征是一种音乐特征表示方法,反映了音频信号中各个音高类(pitch class)的能量分布,音高类是指音高在一个八度内的归类,例如C、C#、D等。
         色度特征通常用于音乐信息检索、和弦识别、音高检测等任务。   """
    chroma = librosa.feature.chroma_cens(y=data, sr=SR, hop_length=HOP_LENGTH, n_chroma=12).T  # (seq_len, 12)
    # print(chroma.shape): (1025, 12)

    """  用于检测音频信号中的起始事件(onsets),并将这些起始事件的位置标记为一个一维的one-hot向量。
         起始事件检测是指,在音频信号中找到能量突然增加的时刻,这些时刻通常对应于音符的开始、鼓点的敲击等。起始强度包络是一个时间序列,
         表示音频信号中每个时间点的起始强度,高起始强度通常意味着在该时间点有一个显著的起始事件。  """
    peak_idxs = librosa.onset.onset_detect(onset_envelope=envelope.flatten(), sr=SR, hop_length=HOP_LENGTH)
    # print(peak_idxs.shape): (143,)
    peak_onehot = np.zeros_like(envelope, dtype=np.float32)
    peak_onehot[peak_idxs] = 1.0  # (seq_len,)
    # print(peak_onehot.shape): (1025,)

    """  尝试获取音频文件的初始节拍速度(BPM,Beats Per Minute)。如果获取失败,则手动计算节拍速度。
         节拍速度是音乐中的一个重要参数,表示每分钟的节拍数,它用于描述音乐的节奏和速度。librosa.beat.tempo函数用于估计音频信号的节拍速度。  """
    try:
        start_bpm = _get_tempo(audio_name)
    except:
        # determine manually
        start_bpm = lr.beat.tempo(y=lr.load(fpath)[0])[0]
        # print(start_bpm): 92.28515625

    """  librosa库中的beat_track函数用于检测音频信号的节拍(beats),以及估计节拍速度(tempo),返回值有两个,
         tempo表示估计的节拍速度(BPM),beat_ids表示检测到的节拍位置索引数组,每个索引对应一个节拍在起始强度包络中的位置。  """
    tempo, beat_idxs = librosa.beat.beat_track(onset_envelope=envelope, sr=SR, hop_length=HOP_LENGTH, start_bpm=start_bpm, tightness=100,)
    # print(tempo): [94.73684211]
    # print(beat_idxs.shape): (52,)

    beat_onehot = np.zeros_like(envelope, dtype=np.float32)
    beat_onehot[beat_idxs] = 1.0  # (seq_len,)
    # print(beat_onehot.shape): (1025,)

    audio_feature = np.concatenate(
        [envelope[:, None], mfcc, chroma, peak_onehot[:, None], beat_onehot[:, None]],
        axis=-1,
    )
    # print(audio_feature.shape): (1025, 35)

    return audio_feature, save_path


wav_path = "../sliced_wav/003.wav"
wav_feature, _ = extract(fpath=wav_path)
print(wav_feature.shape)


"""
环境配置:
librosa == 0.10.2.post1
soundfile == 0.12.1
Python 3.8.0
"""

4、音乐结构化分析

在音乐和音乐理论中,节拍是时间的基本单位,是脉搏(有规律地重复的时间)。节拍通常被定义为听众在听音乐时用脚趾打拍子的节奏,或者音乐家在演奏时数的数字。

例如,四四拍的拍子为"1 2 3 4, 1 2 3 4 …",这四个节拍分别代表了音乐中的四个基本单位。每小节有四拍,通常第一拍为强拍,第二拍为弱拍,第三拍为次强拍,第四拍又为弱拍。这种强弱强弱的循环构成了四拍子的基本节奏特点。

在音乐中,强拍是节奏中的重音,通常比弱拍更为突出和强调。在四拍子(4/4)中,第一拍是明确的强拍,它标志着小节的开始,具有最强的力度和最重要的地位。在某些节拍类型中,除了第一拍作为强拍外,还可能有次强拍存在。在四拍子中,第三拍通常被视为次强拍,它在力度上虽然不及第一拍,但仍然比第二拍和第四拍要强。

5、节拍提取算法

BearNet是一种常用的节拍提取算法,可用于对音乐进行结构化分析。
论文:BEATNET: CRNN AND PARTICLE FILTERING FOR ONLINE JOINT BEAT DOWNBEAT AND METER TRACKING
代码:https://github.com/mjhydri/BeatNet/tree/main?tab=readme-ov-file
在这里插入图片描述

在调用BeatNet算法对wav文件进行节拍检测时,可通过在重拍位置打标记方便可视化分析:

from BeatNet.BeatNet import BeatNet
import os

estimator = BeatNet(1, mode='offline', inference_model='DBN', plot=['downbeat_particles'], thread=False)


filename = "../raw_wav/"
files = sorted(os.listdir(filename))

for name in files:
    path = os.path.join(filename, name)
    Output = estimator.process(path)
    # print(Output)
    print(len(Output))

    down_beat = [obj[0] for obj in Output if obj[1] in [1.0]]
    print(down_beat)
    print(len(down_beat))


    from pydub import AudioSegment

    def add_marking_tone(original_track, marking_tone, beat):
        """Add marking tone to original track at specified position"""
        # Open original track and marking tone
        original = AudioSegment.from_file(original_track)
        marking = AudioSegment.from_file(marking_tone)
        print(len(original))
        print(len(marking))

        """ overlay 函数默认情况下从当前音频片段的开头开始混合。如果你想在特定的位置开始混合,你需要显式地指定这个位置。 
            函数的 alpha 参数控制了两个音频片段混合时的透明度, 是一个介于01之间的浮点数,它决定了第二个音频片段的相对音量。如果alpha为0,
            那么第二个音频片段将完全静音,只有第一个音频片段会听到;如果alpha为1,那么第二个音频片段将完全盖住第一个音频片段。alpha参数的默认值为1,
            这意味着默认情况下,第二个音频片段的音量与第一个音频片段一样大。"""
        for ind in beat:
            position = 1000 * ind    # 以毫秒为单位
            original = original.overlay(marking, position=position)

        return original

    # Specify paths to original track and marking tone
    original_track_path = path
    marking_tone_path = "tone.wav"

    final_track = add_marking_tone(original_track_path, marking_tone_path, down_beat)

    # Export final track
    final_track.export(name, format="wav")


"""
环境配置:
pip install git+https://github.com/CPJKU/madmom

Pyaudio的安装以及报错解决: https://blog.csdn.net/qq_51688022/article/details/136530274
https://pypi.org/project/PyAudio/#files 
但whl似乎只支持windows操作系统,centos操作系统无法使用.

sudo yum -y install portaudio portaudio-devel
pip install pyaudio 

"""

参考BeatNet Github上的说明可知:

BeatNet是基于AI的最先进的Python库,用于联合音乐节拍、重拍、节奏和节拍跟踪。此repo包括BeatNet神经结构以及本文提出的高效两级级联粒子滤波算法,它提供四种不同的工作模式,如下所示:
(a)Streaming mode:此模式直接从麦克风捕获流式音频;
(b)Real-time mode:在此模式下,实时读取和处理音频文件,立即产生结果;
(c)Online mode:与实时模式类似,在线模式采用相同的因果算法进行音轨处理。但它不是实时读取文件,而是读取速度更快,同时仍产生与实时模式相同的结果;
(d)Offline mode:以离线方式推断节拍和重拍;

输入:原始音频波形对象或目录。通过使用音频目录作为系统输入,系统会自动将音频文件重新采样为22050 Hz。但是,在使用音频对象作为输入的情况下,请确保音频采样率等于22050 Hz。

输出:一个包含节拍和重拍列的向量,分别具有以下形状:numpy.array(num_beats, 2)。

超参数:
(a)model:一个范围在[1, 3]内的标量,用于选择要使用哪些预训练的CRNN模型;
(b)mode:一个字符串,用于确定工作模式,即:“流”、“实时”、“在线"和"离线”;
(c)inference model:一个字符串,用于选择推理方法,"PF"代表因果推理的粒子过滤,“DBN"代表非因果用途的动态贝叶斯网络;
(d)plot:要绘制的字符串列表,可以包括"activations”、“beat_particles”、“downbeat_particles”。请注意,为了加快绘制图形的速度,而不是每帧绘制新的图,之前的图会更新。但是为了确保实时结果,建议不要绘制图或尽可能减少当时的图数;
(e)thread:决定是在主线程还是另一个线程上完成推理;
(f)device:正在使用的设备类型,Cuda或cpu(默认);

参考文献

[1] 音视频开发入门:音频基础:https://blog.jianchihu.net/av-develop-audio-basis.html
[2] https://en.wikipedia.org/wiki/Beat_(music)
[3] 利用Python进行音频信号处理与音乐生成:从基础到进阶:https://bbs.huaweicloud.com/blogs/429609

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值