mirror of
https://github.com/mozilla/DeepSpeech.git
synced 2025-10-26 11:19:39 +00:00
136 lines
5.2 KiB
Python
136 lines
5.2 KiB
Python
import os
|
|
import sox
|
|
import wave
|
|
import tempfile
|
|
import collections
|
|
from webrtcvad import Vad
|
|
|
|
DEFAULT_RATE = 16000
|
|
DEFAULT_CHANNELS = 1
|
|
DEFAULT_WIDTH = 2
|
|
DEFAULT_FORMAT = (DEFAULT_RATE, DEFAULT_CHANNELS, DEFAULT_WIDTH)
|
|
|
|
|
|
def get_audio_format(wav_file):
|
|
return wav_file.getframerate(), wav_file.getnchannels(), wav_file.getsampwidth()
|
|
|
|
|
|
def get_num_samples(audio_data, audio_format=DEFAULT_FORMAT):
|
|
_, channels, width = audio_format
|
|
return len(audio_data) // (channels * width)
|
|
|
|
|
|
def get_duration(audio_data, audio_format=DEFAULT_FORMAT):
|
|
return get_num_samples(audio_data, audio_format) / audio_format[0]
|
|
|
|
|
|
def get_duration_ms(audio_data, audio_format=DEFAULT_FORMAT):
|
|
return get_duration(audio_data, audio_format) * 1000
|
|
|
|
|
|
def convert_audio(src_audio_path, dst_audio_path, file_type=None, audio_format=DEFAULT_FORMAT):
|
|
sample_rate, channels, width = audio_format
|
|
transformer = sox.Transformer()
|
|
transformer.set_output_format(file_type=file_type, rate=sample_rate, channels=channels, bits=width*8)
|
|
transformer.build(src_audio_path, dst_audio_path)
|
|
|
|
|
|
class AudioFile:
|
|
def __init__(self, audio_path, as_path=False, audio_format=DEFAULT_FORMAT):
|
|
self.audio_path = audio_path
|
|
self.audio_format = audio_format
|
|
self.as_path = as_path
|
|
self.open_file = None
|
|
self.tmp_file_path = None
|
|
|
|
def __enter__(self):
|
|
if self.audio_path.endswith('.wav'):
|
|
self.open_file = wave.open(self.audio_path, 'r')
|
|
if get_audio_format(self.open_file) == self.audio_format:
|
|
if self.as_path:
|
|
self.open_file.close()
|
|
return self.audio_path
|
|
return self.open_file
|
|
self.open_file.close()
|
|
_, self.tmp_file_path = tempfile.mkstemp(suffix='.wav')
|
|
convert_audio(self.audio_path, self.tmp_file_path, file_type='wav', audio_format=self.audio_format)
|
|
if self.as_path:
|
|
return self.tmp_file_path
|
|
self.open_file = wave.open(self.tmp_file_path, 'r')
|
|
return self.open_file
|
|
|
|
def __exit__(self, *args):
|
|
if not self.as_path:
|
|
self.open_file.close()
|
|
if self.tmp_file_path is not None:
|
|
os.remove(self.tmp_file_path)
|
|
|
|
|
|
def read_frames(wav_file, frame_duration_ms=30, yield_remainder=False):
|
|
audio_format = get_audio_format(wav_file)
|
|
frame_size = int(audio_format[0] * (frame_duration_ms / 1000.0))
|
|
while True:
|
|
try:
|
|
data = wav_file.readframes(frame_size)
|
|
if not yield_remainder and get_duration_ms(data, audio_format) < frame_duration_ms:
|
|
break
|
|
yield data
|
|
except EOFError:
|
|
break
|
|
|
|
|
|
def read_frames_from_file(audio_path, audio_format=DEFAULT_FORMAT, frame_duration_ms=30, yield_remainder=False):
|
|
with AudioFile(audio_path, audio_format=audio_format) as wav_file:
|
|
for frame in read_frames(wav_file, frame_duration_ms=frame_duration_ms, yield_remainder=yield_remainder):
|
|
yield frame
|
|
|
|
|
|
def vad_split(audio_frames,
|
|
audio_format=DEFAULT_FORMAT,
|
|
num_padding_frames=10,
|
|
threshold=0.5,
|
|
aggressiveness=3):
|
|
sample_rate, channels, width = audio_format
|
|
if channels != 1:
|
|
raise ValueError('VAD-splitting requires mono samples')
|
|
if width != 2:
|
|
raise ValueError('VAD-splitting requires 16 bit samples')
|
|
if sample_rate not in [8000, 16000, 32000, 48000]:
|
|
raise ValueError('VAD-splitting only supported for sample rates 8000, 16000, 32000, or 48000')
|
|
if aggressiveness not in [0, 1, 2, 3]:
|
|
raise ValueError('VAD-splitting aggressiveness mode has to be one of 0, 1, 2, or 3')
|
|
ring_buffer = collections.deque(maxlen=num_padding_frames)
|
|
triggered = False
|
|
vad = Vad(int(aggressiveness))
|
|
voiced_frames = []
|
|
frame_duration_ms = 0
|
|
frame_index = 0
|
|
for frame_index, frame in enumerate(audio_frames):
|
|
frame_duration_ms = get_duration_ms(frame, audio_format)
|
|
if int(frame_duration_ms) not in [10, 20, 30]:
|
|
raise ValueError('VAD-splitting only supported for frame durations 10, 20, or 30 ms')
|
|
is_speech = vad.is_speech(frame, sample_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 > threshold * 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 > threshold * ring_buffer.maxlen:
|
|
triggered = False
|
|
yield b''.join(voiced_frames), \
|
|
frame_duration_ms * max(0, frame_index - len(voiced_frames)), \
|
|
frame_duration_ms * frame_index
|
|
ring_buffer.clear()
|
|
voiced_frames = []
|
|
if len(voiced_frames) > 0:
|
|
yield b''.join(voiced_frames), \
|
|
frame_duration_ms * (frame_index - len(voiced_frames)), \
|
|
frame_duration_ms * (frame_index + 1)
|