Reza2kn's picture
Publish complete experimental Audio8 Q4 model and runtimes
21fd722 verified
Raw History Blame Contribute Delete
6.22 kB
"""Causal bounded PCM speech gate; no ASR weights or transcript decisions."""
import hashlib
from pathlib import Path
import time
import numpy as np
from .stream import PCMReader, StreamProfile, require
MODEL_SHA256='1a153a22f4509e292a94e67d6f9b85e8deb25b4988682b7e174c65279d8788e3'
POLICY='silero_v5_threshold0.5_silence2000ms_preroll400ms_v1'
class Silero:
def __init__(self,path):
import onnxruntime as ort
require(hashlib.sha256(Path(path).read_bytes()).hexdigest()==MODEL_SHA256,'Silero model identity')
options=ort.SessionOptions(); options.intra_op_num_threads=1; options.inter_op_num_threads=1
options.execution_mode=ort.ExecutionMode.ORT_SEQUENTIAL
options.graph_optimization_level=ort.GraphOptimizationLevel.ORT_DISABLE_ALL
self.session=ort.InferenceSession(str(path),sess_options=options,providers=['CPUExecutionProvider'])
self.session.disable_fallback()
require(self.session.get_providers()==['CPUExecutionProvider'],'Silero CPU execution required')
self.state=np.zeros((2,1,128),np.float32); self.context=np.zeros(64,np.float32)
self.sr=np.array(16000,dtype=np.int64); self.version=ort.__version__
def probability(self,frame):
require(frame.shape==(512,) and frame.dtype==np.float32 and np.isfinite(frame).all(),'VAD new512 finite samples')
window=np.concatenate((self.context,frame))[None,:]
probability,state=self.session.run(['output','stateN'],{'input':window,'state':self.state,'sr':self.sr})
require(probability.shape==(1,1) and probability.dtype==np.float32 and np.isfinite(probability).all(),'VAD probability shape/type')
require(state.shape==(2,1,128) and state.dtype==np.float32 and np.isfinite(state).all(),'VAD state shape/type')
p=float(probability[0,0]); require(0<=p<=1,'VAD probability range')
self.state=state; self.context=frame[-64:].copy(); return p
class GatedPCM:
def __init__(self,stream,detector,pace_start=None):
self.reader=PCMReader(stream); self.detector=detector; self.pace_start=pace_start
self.profile=StreamProfile('realtime18',3)
self.read_seconds=self.sleep_seconds=self.vad_seconds=0.
self.vad_frames=self.segment_count=self.selected_samples=self.unconsumed_samples=0
self.finished=False
def __iter__(self):
require(not self.finished,'gated source already consumed')
self.finished=True
reader=self.reader; active=None; previous_end=0; next_index=0; closing=False
while True:
if active is not None:
start,end=self.profile.bounds(next_index)
source_start=active['start_sample']+max(0,start-self.profile.left_samples)
source_end=active['start_sample']+end-self.profile.left_samples
terminal=self.profile.emissions_at_eof(reader.samples-active['start_sample'])
eligible=(next_index<terminal) if reader.eof else source_end<=reader.samples
if eligible:
output=np.zeros(end-start,np.float32)
stop=min(source_end,reader.samples)
if stop>source_start:
require(source_start>=reader.base,'gated source overlap discarded')
offset=source_start-reader.base; count=stop-source_start
require(offset+count<=len(reader.buffer),'gated overlap unavailable')
dst=self.profile.left_samples+source_start-active['start_sample']-start
output[dst:dst+count]=reader.buffer[offset:offset+count]
consumed=max(consumed,stop)
event=dict(kind='window',segment=dict(active),index=next_index,audio=output,
nominal_source_end_sample=source_end,available_sample=reader.samples,eof_drain=source_end>reader.samples)
next_index+=1; yield event; continue
if closing or reader.eof:
unconsumed=reader.samples-consumed
self.selected_samples+=reader.samples-active['start_sample']; self.unconsumed_samples+=unconsumed
yield dict(kind='end',segment=dict(active),end_sample=reader.samples,final_eof=reader.eof,
unconsumed_samples=unconsumed,windows=next_index)
previous_end=reader.samples; active=None
if reader.eof: break
keep=max(0,reader.samples-6400)
if active is not None:
next_start=self.profile.bounds(next_index)[0]
keep=min(keep,active['start_sample']+max(0,next_start-self.profile.left_samples))
keep=max(reader.base,min(keep,reader.samples))
reader.buffer=reader.buffer[keep-reader.base:].copy(); reader.base=keep
before=reader.samples; began=time.monotonic(); reader.ensure(before+512)
self.read_seconds+=time.monotonic()-began
require(len(reader.buffer)<=6912,'gated bounded PCM retention')
if self.pace_start is not None:
duration=max(0.,self.pace_start+reader.samples/16000-time.monotonic())
began=time.monotonic()
if duration:time.sleep(duration)
self.sleep_seconds+=time.monotonic()-began
if reader.samples-before<512:continue
frame=reader.buffer[before-reader.base:before-reader.base+512]
began=time.monotonic(); p=self.detector(frame); self.vad_seconds+=time.monotonic()-began
require(type(p) is not bool and np.isfinite(p) and 0<=p<=1,'VAD finite probability')
self.vad_frames+=1
if p>=.5:
last_voice=reader.samples
if active is None:
active=dict(id=self.segment_count,start_sample=max(previous_end,reader.samples-6400),decision_sample=reader.samples)
self.segment_count+=1; next_index=0; closing=False; consumed=active['start_sample']
yield dict(kind='start',segment=dict(active))
elif active is not None and reader.samples-last_voice>=32000:closing=True
yield dict(kind='complete')