如何从pydub的AudioSegment创建一个numpy数组?

37
我知道以下问题:如何使用numpy数组创建pydub AudioSegment?

我的问题与之相反。如果我有一个pydub AudioSegment,我该如何将其转换为numpy数组?
我想使用scipy过滤器等工具。 对于AudioSegment原始数据的内部结构并不是很清楚。
4个回答

23

Pydub有一个获取音频数据的工具,可以将其作为样本数组audio data as an array of samples,它是一个array.array 实例(而不是numpy数组),但您应该能够相对容易地将其转换为numpy数组:

from pydub import AudioSegment
sound = AudioSegment.from_file("sound1.wav")

# this is an array
samples = sound.get_array_of_samples()

你可能能够创建一个numpy的变体来实现这个方法。该方法实现起来非常简单:

def get_array_of_samples(self):
    """
    returns the raw_data as an array of samples
    """
    return array.array(self.array_type, self._data)

从(修改后的?)样本数组中创建新的音频片段也是可能的:

new_sound = sound._spawn(samples)
上述方法有些hacky,因为它是为AudioSegment类内部使用而编写的,但主要用于确定您正在使用的音频数据类型(样本数组、样本列表、字节、字节字符串等)。尽管带有下划线前缀,但仍然可以安全使用。

有没有办法反过来做呢?即在不访问文件系统的情况下,从原始/数组数据动态创建AS对象。 - olix20
1
@olix20,我在我的回答中添加了相关信息。 - Jiaaro
这个问题对我有用,但并没有完全解决我的问题。我发现最简单的转换方式是使用以下代码:audio_segment = pydub.AudioSegment(audio.tobytes(), sample_width=audio.dtype.itemsize, frame_rate=sample_rate, channels=num_channels),然后是np.frombuffer(audio_segment.get_array_of_samples(), dtype=np.float32)。比较原始音频的字节与audio.tobytes()到来自np.frombuffer(...).tobytes()的音频,你会发现它们是相同的。 - Ethan

13

您可以从 AudioSegment 中获取一个 array.array,然后将其转换为 numpy.ndarray

from pydub import AudioSegment
import numpy as np
song = AudioSegment.from_mp3('song.mp3')
samples = song.get_array_of_samples()
samples = np.array(samples)

15
为了让数组适合于scipy滤波器,需要进行形状和排序。在上面的代码块之后,你可能需要这样做:samples = samples.reshape(song.channels, -1, order='F'); samples.shape # (<probably 2>, <len(song) in samples>)。然后,samples波形就可以用于过滤、FFT分析、绘图等操作(虽然你可能需要将其转换为浮点数)。 - user2561747
这个评论非常有帮助,结合答案...解决了我的问题。 - Sachin Kumar
代码中分号后面的注释是必要的吗? - Chris P
@ChrisP 不,这不是必要的 - 只是为了解释。 - Bastian Ebeling

9

现有的答案都不完美,它们忽略了重塑和样本宽度。我编写了这个函数,帮助将音频转换为np中的标准音频表示:

def pydub_to_np(audio: pydub.AudioSegment) -> (np.ndarray, int):
    """
    Converts pydub audio segment into np.float32 of shape [duration_in_seconds*sample_rate, channels],
    where each value is in range [-1.0, 1.0]. 
    Returns tuple (audio_np_array, sample_rate).
    """
    return np.array(audio.get_array_of_samples(), dtype=np.float32).reshape((-1, audio.channels)) / (
            1 << (8 * audio.sample_width - 1)), audio.frame_rate


5

get_array_of_samples(未在[ReadTheDocs.AudioSegment]: audiosegment模块上找到)返回一个一维数组,这不太好用,因为它会丢失有关音频流的信息(帧、通道等)。

几天前,我遇到了这个问题,而我使用了[PyPI]:sounddevice(它期望一个numpy.ndarray)来播放声音(我需要在不同的输出音频设备上播放它)。这是我的解决方法。

code00.py

#!/usr/bin/env python

import sys
from pprint import pprint as pp

import numpy as np
import pydub
import sounddevice as sd


def audio_file_to_np_array(file_name):
    asg = pydub.AudioSegment.from_file(file_name)
    dtype = getattr(np, "int{:d}".format(asg.sample_width * 8))  # Or could create a mapping: {1: np.int8, 2: np.int16, 4: np.int32, 8: np.int64}
    arr = np.ndarray((int(asg.frame_count()), asg.channels), buffer=asg.raw_data, dtype=dtype)
    print("\n", asg.frame_rate, arr.shape, arr.dtype, arr.size, len(asg.raw_data), len(asg.get_array_of_samples()))  # @TODO: Comment this line!!!
    return arr, asg.frame_rate


def main(*argv):
    pp(sd.query_devices())  # @TODO: Comment this line!!!
    a, fr = audio_file_to_np_array("./test00.mp3")
    dvc = 5  # Index of an OUTPUT device (from sd.query_devices() on YOUR machine)
    #sd.default.device = dvc  # Change default OUTPUT device
    sd.play(a, samplerate=fr)
    sd.wait()


if __name__ == "__main__":
    print("Python {:s} {:03d}bit on {:s}\n".format(" ".join(elem.strip() for elem in sys.version.split("\n")),
                                                   64 if sys.maxsize > 0x100000000 else 32, sys.platform))
    rc = main(*sys.argv[1:])
    print("\nDone.")
    sys.exit(rc)

输出:

[cfati@CFATI-5510-0:e:\Work\Dev\StackOverflow\q038015319]> set PATH=%PATH%;f:\Install\pc064\FFMPEG\FFMPEG\4.3.1\bin

[cfati@CFATI-5510-0:e:\Work\Dev\StackOverflow\q038015319]> dir /b
code00.py
test00.mp3

[cfati@CFATI-5510-0:e:\Work\Dev\StackOverflow\q038015319]> "e:\Work\Dev\VEnvs\py_pc064_03.09.01_test0\Scripts\python.exe" code00.py
Python 3.9.1 (tags/v3.9.1:1e5d33e, Dec  7 2020, 17:08:21) [MSC v.1927 64 bit (AMD64)] 064bit on win32

   0 Microsoft Sound Mapper - Input, MME (2 in, 0 out)
>  1 Microphone (Logitech USB Headse, MME (2 in, 0 out)
   2 Microphone (Realtek Audio), MME (2 in, 0 out)
   3 Microsoft Sound Mapper - Output, MME (0 in, 2 out)
<  4 Speakers (Logitech USB Headset), MME (0 in, 2 out)
   5 Speakers / Headphones (Realtek , MME (0 in, 2 out)
   6 Primary Sound Capture Driver, Windows DirectSound (2 in, 0 out)
   7 Microphone (Logitech USB Headset), Windows DirectSound (2 in, 0 out)
   8 Microphone (Realtek Audio), Windows DirectSound (2 in, 0 out)
   9 Primary Sound Driver, Windows DirectSound (0 in, 2 out)
  10 Speakers (Logitech USB Headset), Windows DirectSound (0 in, 2 out)
  11 Speakers / Headphones (Realtek Audio), Windows DirectSound (0 in, 2 out)
  12 Realtek ASIO, ASIO (2 in, 2 out)
  13 Speakers (Logitech USB Headset), Windows WASAPI (0 in, 2 out)
  14 Speakers / Headphones (Realtek Audio), Windows WASAPI (0 in, 2 out)
  15 Microphone (Logitech USB Headset), Windows WASAPI (1 in, 0 out)
  16 Microphone (Realtek Audio), Windows WASAPI (2 in, 0 out)
  17 Microphone (Realtek HD Audio Mic input), Windows WDM-KS (2 in, 0 out)
  18 Speakers (Realtek HD Audio output), Windows WDM-KS (0 in, 2 out)
  19 Stereo Mix (Realtek HD Audio Stereo input), Windows WDM-KS (2 in, 0 out)
  20 Microphone (Logitech USB Headset), Windows WDM-KS (1 in, 0 out)
  21 Speakers (Logitech USB Headset), Windows WDM-KS (0 in, 2 out)

 44100 (82191, 2) int16 164382 328764 164382

--- (Manually inserted line) Sound is playing :) ---

Done.

注:

  • 正如所见,没有硬编码的值(在维度、dtype等方面)

  • 我还需要返回采样率(因为它不能嵌入到数组中),并且设备需要它(在这种情况下是默认的44.1k,但是我已经测试过具有一半该值的文件)

  • 现有的所有答案都使用float来表示采样。这对我来说不起作用,因为对于大多数测试文件,采样率为16bit长,而np.float16不受我的支持,因此我必须使用int

  • 另外,当在各种文件上进行测试时,我的Win笔记本电脑无法通过SoundDevice播放.m4a,很可能是因为32k采样率,但是PyDub可以播放


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