下采样 wav 音频文件

Posted

技术标签:

【中文标题】下采样 wav 音频文件【英文标题】:Downsampling wav audio file 【发布时间】:2015-08-17 15:24:42 【问题描述】:

我必须在不使用任何外部 Python 库的情况下将 wav 文件从 44100Hz 降采样到 16000Hz,因此最好使用wave 和/或audioop。我尝试使用 setframerate 函数将 wav 文件的帧速率更改为 16000,但这只会减慢整个录制的速度。如何将音频文件下采样到 16kHz 并保持相同的音频长度?

【问题讨论】:

如果你降到 11025Hz 会更容易,只需低通滤波器,然后每 4 个采样一次 audioop 的 ratecv 是你想要的吗? docs.python.org/2/library/audioop.html#audioop.ratecv 它需要为 16kHz,因为我们的管道工具需要为 Unity 项目导出它。你介意给我一个使用 audioop.ratecv 函数的例子吗?因为我对该函数的片段参数感到困惑。我如何得到它? @JimJeffries 【参考方案1】:

首先,您需要导入“librosa”库 使用“librosa.load”重新采样音频文件 librosa.load(path,sr) initiallly sr(sampling rate) = 22050.If 你想保持原生采样率使 sr=None。否则音频将被重新采样到提供的采样率

【讨论】:

【参考方案2】:

要对信号进行下采样(也称为decimate)(这意味着降低采样率)或上采样(提高采样率),您需要在数据之间进行插值。

这个想法是你需要以某种方式在你的点之间绘制一条曲线,然后以新的采样率从这条曲线中获取值。这是因为您想知道某个时间未采样的声波的值,因此您必须以一种或另一种方式猜测该值。唯一容易进行二次采样的情况是当您将采样率除以整数 $k$ 时。在这种情况下,您只需要获取 $k$ 样本并只保留第一个样本。但这不会回答你的问题。请参见下图,其中有一条以两种不同比例采样的曲线。

如果您了解原理,您可以手动完成,但我强烈建议您使用库。原因是以正确的方式进行插值并不容易,也不明显。

您可以使用线性插值(用一条线连接点)或二项式插值(用一条多项式连接三个点)或(有时最适合声音)使用傅里叶变换并在频率空间中进行插值。 由于傅立叶变换不是您想要手动重写的东西,如果您想要一个好的二次采样/二次采样, 请参阅下图,了解使用与 scipy 不同的算法的两条上采样曲线。 “重采样”功能使用傅立叶变换。

我确实是在加载 44100Hz 波形文件并需要 48000Hz 采样数据的情况下,所以我写了以下几行来加载我的数据:

    # Imports
    from scipy.io import wavfile
    import scipy.signal as sps

    # Your new sampling rate
    new_rate = 48000

    # Read file
    sampling_rate, data = wavfile.read(path)

    # Resample data
    number_of_samples = round(len(data) * float(new_rate) / sampling_rate)
    data = sps.resample(data, number_of_samples)

请注意,如果您只进行下采样并且想要比傅立叶更快的东西,您也可以使用方法 decimate。

【讨论】:

有没有人有这个意见? “scipy.signal.resample 对音频重采样很糟糕。这很快就变得很明显 - 它在频域中工作,基本上是在频域中截断或补零信号。这在时域中非常难看(特别是因为它假设信号是循环的)。”来源:signalsprocessed.blogspot.com/2016/08/… @MatthewWalker 您可以使用scipy.signal.resample_poly 在时域中使用多项式。 resample 在频域中起作用,您可以显式控制傅里叶变换使用的window。对于 resample_poly,您可以使用 padtypecval 控制填充。我认为只有在重采样中确实看到伪影时,才需要根据需要调整参数。这最终取决于您使用的信号类型。 @MatthewWalker 来自 Scipy 文档:The argument window controls a Fourier-domain window that tapers the Fourier spectrum before zero-padding to alleviate ringing in the resampled values for sampled signals you didn’t intend to be interpreted as band-limited.【参考方案3】:

我尝试使用 Librosa,但由于某些原因,即使在给出 y, s = librosa.load('test.wav', sr=16000)librosa.output.write_wav(filename, y, sr) 行之后,声音文件也没有以给定的采样率(16000,从 44kHz 下采样)保存。 但是pydub 效果很好。 jiaaro 的一个很棒的库,我使用了以下命令:

from pydub import Audiosegment as am
sound = am.from_file(filepath, format='wav', frame_rate=22050)
sound = sound.set_frame_rate(16000)
sound.export(filepath, format='wav')

上面的代码指出,我读取的帧速率为 22050 的文件更改为速率为 16000,export 函数用该文件覆盖现有文件,并使用新的帧速率。它比 librosa 工作得更好,但我正在寻找比较两个包之间速度的方法,但由于我的数据非常少,所以还没有弄清楚!!!

参考:https://github.com/jiaaro/pydub/issues/232

【讨论】:

Librosa 自 0.8 版以来已删除 write_wav 。建议现在使用 soundfile.write。【参考方案4】:

你可以使用 Librosa 的 load() 函数,

import librosa    
y, s = librosa.load('test.wav', sr=8000) # Downsample 44.1kHz to 8kHz

安装 Librosa 的额外努力可能值得高枕无忧。

专业提示:在 Anaconda 上安装 Librosa 时,还需要install ffmpeg,所以

pip install librosa
conda install -c conda-forge ffmpeg

这样可以避免 NoBackendError() 错误。

【讨论】:

可能是这里最好的评论,而且似乎也是最新的。只是缺少OP要求的save,就像librosa.output.write_wav(filename, y, sr)一样简单。 Librosa 自 0.8 版以来已删除 write_wav 。建议现在使用 soundfile.write。 @Austin - 按照 Austin 的建议,write_wav 已被删除,但如果有人仍想使用旧的 librosa 版本,请参阅answer【参考方案5】:

谢谢大家的回答。我已经找到了一个解决方案,而且效果很好。这是整个函数。

def downsampleWav(src, dst, inrate=44100, outrate=16000, inchannels=2, outchannels=1):
    if not os.path.exists(src):
        print 'Source not found!'
        return False

    if not os.path.exists(os.path.dirname(dst)):
        os.makedirs(os.path.dirname(dst))

    try:
        s_read = wave.open(src, 'r')
        s_write = wave.open(dst, 'w')
    except:
        print 'Failed to open files!'
        return False

    n_frames = s_read.getnframes()
    data = s_read.readframes(n_frames)

    try:
        converted = audioop.ratecv(data, 2, inchannels, inrate, outrate, None)
        if outchannels == 1:
            converted = audioop.tomono(converted[0], 2, 1, 0)
    except:
        print 'Failed to downsample wav'
        return False

    try:
        s_write.setparams((outchannels, 2, outrate, 0, 'NONE', 'Uncompressed'))
        s_write.writeframes(converted)
    except:
        print 'Failed to write wav'
        return False

    try:
        s_read.close()
        s_write.close()
    except:
        print 'Failed to close wav files'
        return False

    return True

【讨论】:

我知道这是旧的,但我也遇到了同样的问题,所以我尝试了代码,我认为它有一个微妙的错误。如果我的 inchannels=1 和 outchannels=1 无论如何都会调用 tomono 函数,这会扰乱我的音频信号(长度减半)。另外,在编写帧时,您不应该只编写 convert[0] (取决于是否明显调用了 tomono),因为 ratecv 返回的 newstate 无关紧要吗? 以上模块都在std lib中【参考方案6】:

您可以在scipy 中使用重采样。这样做有点头疼,因为在 Python 原生的 bytestringscipy 中需要的数组之间需要进行一些类型转换。还有一个令人头疼的问题,因为在 Python 的 wave 模块中,无法判断数据是否已签名(仅当它是 8 位或 16 位时)。它可能(应该)对两者都有效,但我还没有测试过。

这是一个将(无符号)8 位和 16 位单声道从 44.1 转换为 16 的小程序。如果您有立体声或使用其他格式,那么适应起来应该不难。在代码开头编辑输入/输出名称。从来没有使用过命令行参数。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
#  downsample.py
#  
#  Copyright 2015 John Coppens <john@jcoppens.com>
#  
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
#  
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#  
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software
#  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
#  MA 02110-1301, USA.
#  
#

inwave = "sine_44k.wav"
outwave = "sine_16k.wav"

import wave
import numpy as np
import scipy.signal as sps

class DownSample():
    def __init__(self):
        self.in_rate = 44100.0
        self.out_rate = 16000.0

    def open_file(self, fname):
        try:
            self.in_wav = wave.open(fname)
        except:
            print("Cannot open wav file (%s)" % fname)
            return False

        if self.in_wav.getframerate() != self.in_rate:
            print("Frame rate is not %d (it's %d)" % \
                  (self.in_rate, self.in_wav.getframerate()))
            return False

        self.in_nframes = self.in_wav.getnframes()
        print("Frames: %d" % self.in_wav.getnframes())

        if self.in_wav.getsampwidth() == 1:
            self.nptype = np.uint8
        elif self.in_wav.getsampwidth() == 2:
            self.nptype = np.uint16

        return True

    def resample(self, fname):
        self.out_wav = wave.open(fname, "w")
        self.out_wav.setframerate(self.out_rate)
        self.out_wav.setnchannels(self.in_wav.getnchannels())
        self.out_wav.setsampwidth (self.in_wav.getsampwidth())
        self.out_wav.setnframes(1)

        print("Nr output channels: %d" % self.out_wav.getnchannels())

        audio = self.in_wav.readframes(self.in_nframes)
        nroutsamples = round(len(audio) * self.out_rate/self.in_rate)
        print("Nr output samples: %d" %  nroutsamples)

        audio_out = sps.resample(np.fromstring(audio, self.nptype), nroutsamples)
        audio_out = audio_out.astype(self.nptype)

        self.out_wav.writeframes(audio_out.copy(order='C'))

        self.out_wav.close()

def main():
    ds = DownSample()
    if not ds.open_file(inwave): return 1
    ds.resample(outwave)
    return 0

if __name__ == '__main__':
    main()

【讨论】:

以上是关于下采样 wav 音频文件的主要内容,如果未能解决你的问题,请参考以下文章

如何重新采样 wav 文件

我如何采样音频文件说 .wav ,专门将其导入数组并将其分成块。 (在 Lua 中)

多媒体文件格式:PCM / WAV 格式

在 Ubuntu 中转换音频文件采样率的脚本

将任意音频格式文件转换成16K采样率16bit的wav文件

使用 afconvert 对 wav (LEI16) 文件进行下采样