如何绘制一个wav文件

61

我刚刚使用Scipy读取了一个wav文件,现在我想使用Matplotlib制作该文件的图形,在“y轴”上我想看到振幅,在“x轴”上我想看到帧数! 有什么帮助可以告诉我如何做到这一点吗?

from scipy.io.wavfile import read
import numpy as np
from numpy import*
import matplotlib.pyplot as plt
a=read("C:/Users/Martinez/Desktop/impulso.wav")
print a

print a 输出什么? - Matthew Turner
这是单声道还是多声道的wav文件? - Phillip Cloud
打印a,只显示带有音频文件原始数据的元组。它是单声道wav文件。 - Diego Martínez Giardini
8个回答

89

你可以调用 wave 库来读取音频文件。

要绘制波形图,使用 matplotlib 的 "plot" 函数即可。

import matplotlib.pyplot as plt
import numpy as np
import wave
import sys


spf = wave.open("wavfile.wav", "r")

# Extract Raw Audio from Wav File
signal = spf.readframes(-1)
signal = np.fromstring(signal, "Int16")


# If Stereo
if spf.getnchannels() == 2:
    print("Just mono files")
    sys.exit(0)

plt.figure(1)
plt.title("Signal Wave...")
plt.plot(signal)
plt.show()

你将会拥有如下图像:在此输入图像描述

要将x轴标注为秒,你需要获得音频帧率并除以信号的大小,可以使用numpy中的linspace函数创建一个时间向量,该向量与音频文件的大小线性间距相等,最后再次使用plot函数,像这样:plt.plot(Time,signal)

import matplotlib.pyplot as plt
import numpy as np
import wave
import sys


spf = wave.open("Animal_cut.wav", "r")

# Extract Raw Audio from Wav File
signal = spf.readframes(-1)
signal = np.fromstring(signal, "Int16")
fs = spf.getframerate()

# If Stereo
if spf.getnchannels() == 2:
    print("Just mono files")
    sys.exit(0)


Time = np.linspace(0, len(signal) / fs, num=len(signal))

plt.figure(1)
plt.title("Signal Wave...")
plt.plot(Time, signal)
plt.show()

新的图表X轴以秒为单位:

图片描述


那很完美,但如果我想在 x 轴上以秒为单位查看时间怎么办?如何实现这个功能? - Diego Martínez Giardini
Ederwander,我不知道为什么当我绘制我的文件时,它只显示数据的反向!我已经复制了你写的一样!有什么建议吗? - Diego Martínez Giardini
如果你真的认为数据被倒置了,使用numpy的任何函数来转置你的向量。 - ederwander
出于好奇,我将结果与Python和Audacity中的图形进行了比较,您可以在此处看到相同的波形:http://i.stack.imgur.com/HRbAE.png。 - ederwander
@ederwander 如果我想让这个图表变成交互式的,该怎么做呢?也就是说,如果我在图像上悬停,它应该显示该点的值。谢谢。 - kRazzy R
显示剩余3条评论

31

另外,如果您想使用SciPy,也可以按照以下步骤进行:

from scipy.io.wavfile import read
import matplotlib.pyplot as plt

# read audio samples
input_data = read("Sample.wav")
audio = input_data[1]
# plot the first 1024 samples
plt.plot(audio[0:1024])
# label the axes
plt.ylabel("Amplitude")
plt.xlabel("Time")
# set the title  
plt.title("Sample Wav")
# display the plot
plt.show()

1
有没有关于如何编辑处理24位深度WAV文件的建议? - thron of three
很棒的回答@CuriousCoder!如果这是针对mp3文件完成的,那么Y轴上的单位会是分贝吗? - maximus

18

这是一个基于@ederwander的回答,还可以处理立体声输入的版本。

import matplotlib.pyplot as plt
import numpy as np
import wave

file = 'test.wav'

with wave.open(file,'r') as wav_file:
    #Extract Raw Audio from Wav File
    signal = wav_file.readframes(-1)
    signal = np.fromstring(signal, 'Int16')

    #Split the data into channels 
    channels = [[] for channel in range(wav_file.getnchannels())]
    for index, datum in enumerate(signal):
        channels[index%len(channels)].append(datum)

    #Get time from indices
    fs = wav_file.getframerate()
    Time=np.linspace(0, len(signal)/len(channels)/fs, num=len(signal)/len(channels))

    #Plot
    plt.figure(1)
    plt.title('Signal Wave...')
    for channel in channels:
        plt.plot(Time,channel)
    plt.show()

在此输入图片描述


功能正常,但速度比较慢。可以使用一行代码 channels = [signal[channel::num_channels] for channel in range(num_channels)]来让它跑得非常快。 - Arthur C

18

这里是绘制波形和频谱的音频文件代码

import wave
import numpy as np
import matplotlib.pyplot as plt

signal_wave = wave.open('voice.wav', 'r')
sample_rate = 16000
sig = np.frombuffer(signal_wave.readframes(sample_rate), dtype=np.int16)

对于整个波形文件的部分

sig = sig[:]

对于音频文件的部分片段

sig = sig[25000:32000]

分离立体声通道

left, right = data[0::2], data[1::2]

绘制波形图(plot_a)和频率谱图(plot_b)

plt.figure(1)

plot_a = plt.subplot(211)
plot_a.plot(sig)
plot_a.set_xlabel('sample rate * time')
plot_a.set_ylabel('energy')

plot_b = plt.subplot(212)
plot_b.specgram(sig, NFFT=1024, Fs=sample_rate, noverlap=900)
plot_b.set_xlabel('Time')
plot_b.set_ylabel('Frequency')

plt.show()

波形信号和信号的频谱图


1
什么是“数据”? - meniluca
这是一个打字错误...将"data"替换为"sig",它就可以正常工作了!谢谢@nikhil parashar。 - chad steele

16

仅仅是一条观察(我无法添加评论)。

你将收到以下信息:

DeprecationWarning:数字类型代码已被弃用,并且将来会产生错误。

不要使用np.fromstring来处理二进制数据。建议使用 signal = np.frombuffer(signal, dtype='int16'),而不是 signal = np.fromstring(signal, 'Int16')


5

以下是一个可以处理单声道/立体声和8位/16位PCM的版本。

import matplotlib.pyplot as plt
import numpy as np
import wave

file = 'test.wav'

wav_file = wave.open(file,'r')

#Extract Raw Audio from Wav File
signal = wav_file.readframes(-1)
if wav_file.getsampwidth() == 1:
    signal = np.array(np.frombuffer(signal, dtype='UInt8')-128, dtype='Int8')
elif wav_file.getsampwidth() == 2:
    signal = np.frombuffer(signal, dtype='Int16')
else:
    raise RuntimeError("Unsupported sample width")

# http://schlameel.com/2017/06/09/interleaving-and-de-interleaving-data-with-python/
deinterleaved = [signal[idx::wav_file.getnchannels()] for idx in range(wav_file.getnchannels())]

#Get time from indices
fs = wav_file.getframerate()
Time=np.linspace(0, len(signal)/wav_file.getnchannels()/fs, num=len(signal)/wav_file.getnchannels())

#Plot
plt.figure(1)
plt.title('Signal Wave...')
for channel in deinterleaved:
    plt.plot(Time,channel)
plt.show()

2
我想我本来可以在评论中发表这个问题,但是基于@ederwander和@TimSC的答案,我想要做出更细致和美观的东西。下面的代码可以创建一个非常漂亮的立体声或单声道波形文件(我不需要标题,所以我注释了它,也不需要show方法 - 只需要保存图像文件)。
以下是渲染的立体声wav示例: enter image description here 还有代码,我提到的差异:
import matplotlib.pyplot as plt
import numpy as np
import wave

file = '/Path/to/my/audio/file/DeadMenTellNoTales.wav'

wav_file = wave.open(file,'r')

#Extract Raw Audio from Wav File
signal = wav_file.readframes(-1)
if wav_file.getsampwidth() == 1:
    signal = np.array(np.frombuffer(signal, dtype='UInt8')-128, dtype='Int8')
elif wav_file.getsampwidth() == 2:
    signal = np.frombuffer(signal, dtype='Int16')
else:
    raise RuntimeError("Unsupported sample width")

# http://schlameel.com/2017/06/09/interleaving-and-de-interleaving-data-with-python/
deinterleaved = [signal[idx::wav_file.getnchannels()] for idx in range(wav_file.getnchannels())]

#Get time from indices
fs = wav_file.getframerate()
Time=np.linspace(0, len(signal)/wav_file.getnchannels()/fs, num=len(signal)/wav_file.getnchannels())
plt.figure(figsize=(50,3))
#Plot
plt.figure(1)
#don't care for title
#plt.title('Signal Wave...')
for channel in deinterleaved:
    plt.plot(Time,channel, linewidth=.125)
#don't need to show, just save
#plt.show()
plt.savefig('/testing_folder/deadmentellnotales2d.png', dpi=72)

0

我想到了一种更灵活、更高效的解决方案:

  • 使用下采样实现每秒两个样本。这是通过计算每个窗口的绝对值的平均值来实现的。结果看起来像流媒体网站(如SoundCloud)中的波形。
  • 支持多通道(感谢@Alter)
  • 每个操作都使用Numpy完成,比循环遍历数组要快得多。
  • 文件被分批处理,以支持非常大的文件。
import matplotlib.pyplot as plt
import numpy as np
import wave
import math

file = 'audiofile.wav'

with wave.open(file,'r') as wav_file:
    num_channels = wav_file.getnchannels()
    frame_rate = wav_file.getframerate()
    downsample = math.ceil(frame_rate * num_channels / 2) # Get two samples per second!

    process_chunk_size = 600000 - (600000 % frame_rate)

    signal = None
    waveform = np.array([])

    while signal is None or signal.size > 0:
        signal = np.frombuffer(wav_file.readframes(process_chunk_size), dtype='int16')

        # Take mean of absolute values per 0.5 seconds
        sub_waveform = np.nanmean(
            np.pad(np.absolute(signal), (0, ((downsample - (signal.size % downsample)) % downsample)), mode='constant', constant_values=np.NaN).reshape(-1, downsample),
            axis=1
        )

        waveform = np.concatenate((waveform, sub_waveform))

    #Plot
    plt.figure(1)
    plt.title('Waveform')
    plt.plot(waveform)
    plt.show()

1
我在使用np.pad时遇到了错误,提示“ValueError: cannot convert float NaN to integer”,你有什么想法如何解决吗? - JZoares

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接