使用Python在音频频谱上绘制文本

这是一段8秒长的音频,采样率22050。总计8 \times 22050 = 176400个采样点可以完美复现这一段时间内,每个采样时刻空气震动的情况。

将这些采样数据输入电脑扬声器,扬声器按照数据指示的强度和方向压缩空气,使空气震动,就可以发出想要的声音。

使用傅里叶变换(DFT),可以找到这个音频在每一个频率处的强度。

什么是傅里叶变换?下面这个视频给出了一个浅显的解释——

由于这段整整8秒的音频并不是一成不变的。所以计算整个音频在每一个频率上的强度并没有太大的意义。可以将整个音频分成若干段,(在本文的例子中,我们将连续的2048个采样点看做一段),分别计算每一段在每一个频率下强度,最终得到一个二维矩阵,这就是短时傅里叶变换(stft)。

这个图像,红色部分意味着这个一时刻的这一个频率的声音强度较大。整张图片可以理解为声音的指纹,在本文中就用声纹称呼它好了。从声纹中,很容易看到原音频的节拍、音色等听觉属性。

在声纹上,可以绘制自己的文字,比如像这样:

由于在声纹文字的绘制只是将某一个时间点,某个频率强度稍微增强,对原音频来说,只是增加了些许噪音,并不会大幅度影响音质。

由此,我们成功将一句话嵌入了音频中。对于外人来看,仅仅是音频多了一些杂音,但是倘若将音频重新进行stft变换,则可以从频谱中读出消息。


安装python依赖库,其中librosa的使用教程可移步《librosa使用教程

pip install librosa
pip install soundfile

引入文字转图片的函数,见我另一篇文章《Python绘制汉字点阵图(字符图)》,将代码放置到charpics.py中。

下面是生成脚本,包含两个函数

  • 函数analyse_audio可以绘制一个音频的dft、stft变换结果,我们可以用它观察一段音频的声纹。
  • 函数modify_audio则可以通过修改声纹,实现音频的修改。
import numpy as np
import librosa
import math
from charpics import gen_pics
import soundfile as sf

def analyse_audio(
        filename,
        duration = None,
        offset = None
        ):
    import matplotlib.pyplot as plt
    import librosa.display as display
    y, sr = librosa.load(filename, duration=duration, offset=offset)
    print("sample rate = %d" % sr)
    M = librosa.feature.melspectrogram(y=y)
    M_highres = librosa.feature.melspectrogram(y=y, hop_length=512)
    F_raw = librosa.core.stft(y=y)
    F_all = np.fft.fft(y)
    plt.figure(figsize=(6, 8))
    ax = plt.subplot(4, 1, 1)
    ax.plot(y)
    ax = plt.subplot(4, 1, 2)
    ax.plot(F_all)
    ax = plt.subplot(4, 1, 3)
    display.specshow(librosa.power_to_db(M, ref=np.max), y_axis='mel', x_axis='time')
    ax = plt.subplot(4, 1, 4)
    display.specshow(librosa.power_to_db(F_raw, ref=np.max), y_axis='linear', x_axis='time')
    plt.show()

def modify_audio(
        text, # the text you wan't to embed into audio
        input_filename,  # input audio file name(mp3/wav)
        output_filename, # output audio file name (wav)
        font_path,  # the location of system font
        offset = 0, # the start position of the audio
        duration = 5, # the duration of the audio
        strength = 3, # strength of the modification
        plot_figure = False # plot the result via matplotlib
        ):
    y, sr = librosa.load(input_filename, duration=duration, offset=offset)
 
    M_raw = librosa.core.stft(y=y)
    M = M_raw.copy()

    _, mask = gen_pics(text, 2, font_path)
    mask = mask.T[::-1,:].astype(np.float32) / 255
    mask_mul = mask*(strength-1/strength) + 1/strength

    ratio = math.ceil(M.shape[1] / mask.shape[1])

    for i in range(M.shape[0]):
        for j in range(M.shape[1]):
            posi = i/M.shape[0]
            posj = j/M.shape[1]
            posi = int(min(posi * mask.shape[0], mask.shape[0]-1))
            posj = int(min(posj * mask.shape[1], mask.shape[1]-1))
            M[i][j] = M[i][j] * mask_mul[posi][posj]

    yy = librosa.core.istft(M)

    if plot_figure:
        import matplotlib.pyplot as plt
        import librosa.display as display
        plt.figure(figsize=(6, 8))
        ax = plt.subplot(2, 1, 1)
        display.specshow(librosa.power_to_db(M_raw, ref=np.max), y_axis='mel', x_axis='time')
        ax = plt.subplot(2, 1, 2)
        display.specshow(librosa.power_to_db(M, ref=np.max), y_axis='mel', x_axis='time')
        plt.show()
    sf.write(output_filename, yy, sr)

if __name__ == '__main__':
    input_filename = './song.mp3'
    output_filename = './song-modified.wav'
    font_path = "/Library/Fonts/Arial Unicode.ttf"

    modify_audio('气氛蕉啄起来', input_filename, output_filename, font_path,
        strength = 2.5, duration = 8, offset = 20, plot_figure=True)

    analyse_audio(output_filename, duration=None, offset=None)

算法的核心就是使用librosa.core.stftlibrosa.core.istft进行变换。在中间,调用gen_pics函数修正声纹,具体来说,让文字部分的声纹强度乘strength,其他部分除以strength

《使用Python在音频频谱上绘制文本》有一个想法

发表评论

电子邮件地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据