将声音文件作为NumPy数组导入Python的方法(替代audiolab的方法)

13

我过去一直在使用Audiolab导入音频文件,效果还不错。但是:

-

In [2]: from scikits import audiolab
--------------------------------------------------------------------

ImportError                               Traceback (most recent call last)

C:\Python26\Scripts\<ipython console> in <module>()

C:\Python26\lib\site-packages\scikits\audiolab\__init__.py in <module>()
     23 __version__ = _version
     24
---> 25 from pysndfile import formatinfo, sndfile
     26 from pysndfile import supported_format, supported_endianness, \
     27                       supported_encoding, PyaudioException, \

C:\Python26\lib\site-packages\scikits\audiolab\pysndfile\__init__.py in <module>()
----> 1 from _sndfile import Sndfile, Format, available_file_formats, available_encodings
      2 from compat import formatinfo, sndfile, PyaudioException, PyaudioIOError
      3 from compat import supported_format, supported_endianness, supported_encoding

ImportError: DLL load failed: The specified module could not be found.``

所以我想要做以下两件事之一:

  • 找出为什么它在2.6上无法工作(_sndfile.pyd存在问题?),并可能找到一种扩展它以使用不受支持格式的方法
  • 寻找完全替代audiolab的解决方案

问题特定于Windows上的Python 2.6(即您在Python 2.5上看不到它)。我还没有找到解决方法。 - David Cournapeau
2
我最终在两次飞行之间抽出时间,结果发现这是一个mingw的错误。我已发布了一个新的0.11.0版本,应该解决了这个问题。 - David Cournapeau
2
David,你在audiolab中开发了一个非常棒的工具!我经常使用它。谢谢你。 - Steve Tjoa
5个回答

15

在我的 Ubuntu 9.04 上,Audiolab 和 Python 2.6.2 能够正常工作,所以这可能是一个 Windows 的问题。从你提供的论坛链接中可以看出,作者也认为这是一个 Windows 错误。

过去,这个选项对我也有效:

from scipy.io import wavfile
fs, data = wavfile.read(filename)

请注意,data可能具有int数据类型,因此其不在[-1,1)内缩放。例如,如果dataint16,则必须将data除以2 ** 15以在[-1,1)内进行缩放。


我不确定。16位或32位应该没问题,但我不知道24位怎么样。 - Steve Tjoa
它并没有读取到任何内容。即使16位文件也是倒置的,并且对-1的值存在环绕错误。24位则会出现“TypeError: data type not understood”错误。肯定有更好的解决方法... - endolith
你能否发布一个导致错误的文件?此外,测试套件是否正确通过(scikits.audiolab.test())?audiolab使用libsndfile,这是我所知道的最好和最可靠的音频IO库。当然,audiolab本身可能存在错误。 - David Cournapeau
我现在看不到环绕错误了,但那是scipy.io的错误,而不是audiolab的。 - endolith

6

Sox http://sox.sourceforge.net/ 可以帮助你完成这项任务。它可以读取许多不同的格式,并将它们输出为您喜欢的任何数据类型的原始数据。实际上,我刚刚编写了从音频文件中读取一块数据到numpy数组的代码。

我决定选择这条路是为了可移植性(sox非常普遍)和最大化我可以使用的输入音频类型的灵活性。实际上,从初步测试来看,对于我使用它来读取很长(数小时)文件中的短(几秒钟)音频并没有明显变慢。

您需要的变量:

SOX_EXEC # the sox / sox.exe executable filename
filename # the audio filename of course
num_channels # duh... the number of channels
out_byps # Bytes per sample you want, must be 1, 2, 4, or 8

start_samp # sample number to start reading at
len_samp   # number of samples to read

实际的代码非常简单。如果你想提取整个文件,可以删除start_samp、len_samp和“trim”部分。
import subprocess # need the subprocess module
import numpy as NP # I'm lazy and call numpy NP

cmd = [SOX_EXEC,
       filename,              # input filename
       '-t','raw',            # output file type raw
       '-e','signed-integer', # output encode as signed ints
       '-L',                  # output little endin
       '-b',str(out_byps*8),  # output bytes per sample
       '-',                   # output to stdout
       'trim',str(start_samp)+'s',str(len_samp)+'s'] # only extract requested part 

data = NP.fromstring(subprocess.check_output(cmd),'<i%d'%(out_byps))
data = data.reshape(len(data)/num_channels, num_channels) # make samples x channels

PS:这里有使用sox读取音频文件头信息的代码...

    info = subprocess.check_output([SOX_EXEC,'--i',filename])
    reading_comments_flag = False
    for l in info.splitlines():
        if( not l.strip() ):
            continue
        if( reading_comments_flag and l.strip() ):
            if( comments ):
                comments += '\n'
            comments += l
        else:
            if( l.startswith('Input File') ):
                input_file = l.split(':',1)[1].strip()[1:-1]
            elif( l.startswith('Channels') ):
                num_channels = int(l.split(':',1)[1].strip())
            elif( l.startswith('Sample Rate') ):
                sample_rate = int(l.split(':',1)[1].strip())
            elif( l.startswith('Precision') ):
                bits_per_sample = int(l.split(':',1)[1].strip()[0:-4])
            elif( l.startswith('Duration') ):
                tmp = l.split(':',1)[1].strip()
                tmp = tmp.split('=',1)
                duration_time = tmp[0]
                duration_samples = int(tmp[1].split(None,1)[0])
            elif( l.startswith('Sample Encoding') ):
                encoding = l.split(':',1)[1].strip()
            elif( l.startswith('Comments') ):
                comments = ''
                reading_comments_flag = True
            else:
                if( other ):
                    other += '\n'+l
                else:
                    other = l
                if( output_unhandled ):
                    print >>sys.stderr, "Unhandled:",l
                pass

有趣,但有点笨拙,也许不跨平台?有pysox可以直接与libSoX库进行交互。看起来SoX自己支持很多格式,并且可以使用其他几个库进行更多的操作。我在使用audiolab时遇到了很多问题,而且它不支持MP3等格式,因此pysox可能值得一试。 - endolith
2
我会看一下pysox...谢谢。虽然使用sox的子进程方法并不是很符合Python风格或美观,但它非常强大且相对可移植(因为大多数系统都可以找到sox二进制文件/安装程序)。 - travc

5

FFmpeg支持mp3格式,并可在Windows系统上使用 (http://zulko.github.io/blog/2013/10/04/read-and-write-audio-files-in-python-using-ffmpeg/)。

读取mp3文件:

import subprocess as sp

FFMPEG_BIN = "ffmpeg.exe"

command = [ FFMPEG_BIN,
        '-i', 'mySong.mp3',
        '-f', 's16le',
        '-acodec', 'pcm_s16le',
        '-ar', '44100', # ouput will have 44100 Hz
        '-ac', '2', # stereo (set to '1' for mono)
        '-']
pipe = sp.Popen(command, stdout=sp.PIPE, bufsize=10**8)

将数据格式化为numpy数组:

raw_audio = pipe.proc.stdout.read(88200*4)

import numpy

audio_array = numpy.fromstring(raw_audio, dtype="int16")
audio_array = audio_array.reshape((len(audio_array)/2,2))

4

如果你想为MP3做这件事

这是我正在使用的:它使用pydub和scipy。

完整设置(在Mac上,其他系统可能有所不同):

import tempfile
import os
import pydub
import scipy
import scipy.io.wavfile


def read_mp3(file_path, as_float = False):
    """
    Read an MP3 File into numpy data.
    :param file_path: String path to a file
    :param as_float: Cast data to float and normalize to [-1, 1]
    :return: Tuple(rate, data), where
        rate is an integer indicating samples/s
        data is an ndarray(n_samples, 2)[int16] if as_float = False
            otherwise ndarray(n_samples, 2)[float] in range [-1, 1]
    """

    path, ext = os.path.splitext(file_path)
    assert ext=='.mp3'
    mp3 = pydub.AudioSegment.from_mp3(file_path)
    _, path = tempfile.mkstemp()
    mp3.export(path, format="wav")
    rate, data = scipy.io.wavfile.read(path)
    os.remove(path)
    if as_float:
        data = data/(2**15)
    return rate, data

致谢詹姆斯·汤普森的博客


1
你需要使用 os.close(_) (并且可能需要将 _ 重命名为 fd)来关闭临时文件描述符。否则,当在 for 循环中运行时,最终会出现 [Errno 24] Too many open files 错误。 - Matthew D. Scholefield
不必将文件导出为wav格式,再用scipy重新加载,你可以直接转换为numpy数组:data = np.reshape(mp3.get_array_of_samples(), (-1, 2)) - Feodoran
file_pathFILEPATH 发生了什么事? - cardamom

2
最近我一直在使用PySoundFile而不是Audiolab。它可以很容易地通过conda进行安装。
不支持mp3,就像大多数东西一样。 MP3现在不再受专利保护,所以没有理由不能支持它;只需要有人将支持写入libsndfile即可。

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