当前位置: 首页 > 知识库问答 >
问题:

将PCM波形数据转换为numpy数组,反之亦然

何辰沛
2023-03-14

情况

我正在使用WebRTC的VAD(语音活动检测),通过使用一个Python适配器WebRTC VAD。GitHub repo的示例实现使用Python的wave模块从文件中读取PCM数据。请注意,根据注释,该模块仅适用于单声道音频和8000、16000或32000 Hz的采样率。

我想做的事

从具有不同采样率的任意音频文件(MP3和WAV文件)中读取音频数据,将它们转换为WebRTC-VAD正在使用的PCM表示,应用WebRTC-VAD来检测语音活动,最后通过再次生成Numpy-Array来处理结果从PCM数据中,因为它们在使用Librosa时最容易使用

我的问题

WebRTC VAD模块仅在使用wave模块时才能正常工作。此模块将PCM数据作为对象返回。向其馈送已获得的Numpy数组(例如使用librosa)时,它不起作用。加载(…) 。我还没有找到在这两种表示之间转换的方法。

到目前为止我所做的

我编写了以下函数来从音频文件中读取音频数据并自动转换它们:

使用Librosa读取/转换任何音频数据的通用函数(--

def read_audio(file_path, sample_rate=None, mono=False):       
    return librosa.load(file_path, sr=sample_rate, mono=mono)

将任意数据读取为PCM数据的函数(-

def read_audio_vad(file_path):
    audio, rate = librosa.load(file_path, sr=16000, mono=True)
    tmp_file = 'tmp.wav'
    sf.write(tmp_file, audio, rate, subtype='PCM_16')
    audio, rate = read_pcm16_wave(tmp_file)
    remove(tmp_file)
    return audio, rate

def read_pcm16_wave(file_path):    
    with wave.open(file_path, 'rb') as wf:
        sample_rate = wf.getframerate()
        pcm_data = wf.readframes(wf.getnframes())
        return pcm_data, sample_rate

如你所见,我先用librosa读取/转换音频数据,然后绕道而行。这是必要的,所以我可以读取MP3文件或WAV文件的任意编码,并自动重新采样到16kHz单声道与Librosa。然后我正在向一个临时文件写入。在删除文件之前,我再次读取了内容,但这次使用的是wave模块。这将为我提供PCM数据。

现在,我有以下代码来提取语音活动并生成Numpy数组:

def webrtc_voice(audio, rate):
    voiced_frames = webrtc_split(audio, rate)
    tmp_file = 'tmp.wav'
    for frames in voiced_frames:
        voice_audio = b''.join([f.bytes for f in frames])
        write_pcm16_wave(tmp_file, voice_audio, rate)
        voice_audio, rate = read_audio(tmp_file)
        remove(tmp_file)

        start_time = frames[0].timestamp
        end_time = (frames[-1].timestamp + frames[-1].duration)
        start_frame = int(round(start_time * rate / 1e3)) 
        end_frame = int(round(end_time * rate / 1e3)) 
        yield voice_audio, rate, start_frame, end_frame

def write_pcm16_wave(path, audio, sample_rate):
    with wave.open(path, 'wb') as wf:
        wf.setnchannels(1)
        wf.setsampwidth(2)
        wf.setframerate(sample_rate)
        wf.writeframes(audio)

如您所见,我再次绕道一个临时文件,首先写入PCM数据,然后使用Librosa再次读取临时文件以获得一个Numpy数组。webrtc\u split函数是示例实现的实现,只有很少的小改动。为完整起见,我将其张贴在此处:

def webrtc_split(audio, rate, aggressiveness=3, frame_duration_ms=30, padding_duration_ms=300):
    vad = Vad(aggressiveness)

    num_padding_frames = int(padding_duration_ms / frame_duration_ms)
    ring_buffer = collections.deque(maxlen=num_padding_frames)
    triggered = False

    voiced_frames = []
    for frame in generate_frames(audio, rate):
        is_speech = vad.is_speech(frame.bytes, rate)

        if not triggered:
            ring_buffer.append((frame, is_speech))
            num_voiced = len([f for f, speech in ring_buffer if speech])
            if num_voiced > 0.9 * ring_buffer.maxlen:
                triggered = True
                for f, s in ring_buffer:
                    voiced_frames.append(f)
                ring_buffer.clear()
        else:
            voiced_frames.append(frame)
            ring_buffer.append((frame, is_speech))
            num_unvoiced = len([f for f, speech in ring_buffer if not speech])
            if num_unvoiced > 0.9 * ring_buffer.maxlen:
                triggered = False
                yield voiced_frames
                ring_buffer.clear()
                voiced_frames = []
    if voiced_frames:
        yield voiced_frames


class Frame(object):
    """
    object holding the audio signal of a fixed time interval (30ms) inside a long audio signal
    """

    def __init__(self, bytes, timestamp, duration):
        self.bytes = bytes
        self.timestamp = timestamp
        self.duration = duration


def generate_frames(audio, sample_rate, frame_duration_ms=30):
    frame_length = int(sample_rate * frame_duration_ms / 1000) * 2
    offset = 0
    timestamp = 0.0
    duration = (float(frame_length) / sample_rate)
    while offset + frame_length < len(audio):
        yield Frame(audio[offset:offset + frame_length], timestamp, duration)
        timestamp += duration
        offset += frame_length

我的问题

我用wave模块写/读临时文件,用Librosa读/写这些文件以获得Numpy数组,这对我来说太复杂了。然而,尽管花了一整天的时间在这件事上,我还是没有找到在两种编码之间直接转换的方法。我承认,我不完全了解PCM和WAVE文件的所有细节,使用16/24/32位PCM数据的影响或endpoint。我希望我上面的解释足够详细,不要太多。有没有更简单的方法可以在内存中的两种表示形式之间进行转换?

共有1个答案

郎雪风
2023-03-14

WebRTC VAD和Python包装器py webrtcvad似乎希望音频数据是16位PCM little endian,这是WAV文件中最常见的存储格式。

librosa及其底层I/O库pysoundfile但是总是返回范围[-1.0,1.0]中的浮点数组。要将其转换为包含16位PCM的字节,您可以使用以下float_to_pcm16函数

def float_to_pcm16(audio):
    import numpy

    ints = (audio * 32767).astype(numpy.int16)
    little_endian = ints.astype('<u2')
    buf = little_endian.tostring()
    return buf


def read_pcm16(path):
    import soundfile

    audio, sample_rate = soundfile.read(path)
    assert sample_rate in (8000, 16000, 32000, 48000)
    pcm_data = float_to_pcm16(audio)
    return pcm_data, sample_rate
 类似资料:
  • 问题内容: 我正在寻找一种将NumPy数组传递给Matlab的方法。 我已经设法通过使用将数组存储到图像中,然后使用加载它来做到这一点,但这当然会使矩阵包含0到256之间的值,而不是“真实”值。 将这个矩阵的乘积除以256,再加上原始NumPy数组中的最大值,可以得出正确的矩阵,但是我觉得这有点乏味。 有没有更简单的方法? 问题答案: 当然,只要使用 举个例子: 同样,有。 然后,您使用将其加载到

  • 问题内容: 我发现了将文件转换为字节数组并将字节数组写入存储中文件的多种方法。 我想要的是转换为字节数组,然后将字节数组转换回。 我不想像以下那样将其写出到存储中: 我想以某种方式执行以下操作: 问题答案: 我认为您误解了班级的真正含义。它只是系统上文件的表示,即文件名,路径等。 您甚至看过该课程的Javadoc 吗?在这里看看, 如果检查它具有的字段或方法或构造函数参数,您会立即得到暗示,它只是

  • 问题内容: 我正在尝试通过android将图像作为字节数组发送,并在服务器端将其转换回png文件,我正在服务器端。 这是我的android代码,它将图像转换为字节数组并将其作为请求发送到服务器: 这是我的服务器端程序: 服务器上的异常是: 问题答案: 您将图像写入客户端的文件输出流,该流会将内容存储到物理文件中。然后,您尝试将的表示形式转换为图像内容。这是行不通的,该类的方法不会为您提供写入流中的

  • 问题内容: 我想将一些数据存储到Java中的字节数组中。基本上只是数字,每个数字最多占用2个字节。 我想知道如何将整数转换为2字节长的字节数组,反之亦然。我发现有很多解决方案在使用Google搜索,但是大多数解决方案没有解释代码中会发生什么。我真的不了解很多变化的东西,所以我希望能得到一个基本的解释。 问题答案: 将带符号的字节打包为一个int时,每个字节都需要屏蔽掉,因为由于算术提升规则(在JL

  • 问题内容: 我正在尝试将a 转换为原始类型,反之亦然: 奇怪的是,当我尝试将新分配的分配回a时,结果不过是垃圾。 但是,当我使用原始数据类型作为参数时,似乎相同的算法工作得很好。 问题答案: 改用这些。

  • 问题内容: 我将如何使用python将IP地址转换为十进制数字,反之亦然? 例如,对于,我想使用易于存储在数据库中的十进制或二进制形式,然后检索它。 问题答案: 将IP字符串转换为长整数: 另一种方式: