状况
我正在使用WebRTC的VAD(语音活动检测),使用的方法是 WebRTC-VAD,一个Python适配器。该 实例 的GitHub repo中使用了Python的 波形模块 来读取文件中的PCM数据。请注意,根据注释,该模块只适用于单声道音频和8000、16000或32000 Hz的采样率。
我想做的是
从任意音频文件(MP3和WAV文件)中读取不同采样率的音频数据,将其转换为WebRTC-VAD所使用的PCM表示法,应用WebRTC-VAD检测语音活动,最后通过从PCM数据再次生成Numpy-Array来处理结果,因为在使用PCM数据时,它们是最容易处理的。书籍
我的问题
WebRTC-VAD模块只有在使用以下功能时才能正常工作 wave
模块。该模块将PCM数据以 bytes
对象。当给它输入已经获得的Numpy数组时,它不能工作,例如通过使用 librosa.load(...)
. 我还没有找到一种方法来转换这两种表达方式。
到目前为止,我所做的是
我写了以下函数来读取音频文件中的音频数据并自动转换它们。
用Librosa读取转换任何音频数据的通用函数(-->;返回Numpy数组)。
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文件中读取任意编码的数据,然后用Librosa自动将其重新采样为16kHz单声道。然后我在向一个临时文件写入。在删除文件之前,我再次读出文件的内容,但这次我使用的是 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_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 Arrays,对我来说似乎过于复杂。然而,尽管我花了整整一天的时间在这件事上,我没有找到一种方法来直接在两种编码之间进行转换。我承认我并不完全理解PCM和WAVE文件的所有细节,对PCM数据使用162432-Bit的影响或endianness。我希望我上面的解释足够详细,不要太多。有没有更简单的方法在内存中转换这两种表示方式?
看来,WebRTC-VAD,还有Python包装器。py-wrtcvad,希望音频数据是16位PCM小恩典--因为这是WAV文件中最常见的存储格式。
librosa
和它的底层IO库 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