File size: 3,033 Bytes
139f25e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
import numpy as np
import struct

EPS_F32 = struct.unpack('<f', struct.pack('<I', 0x33D6BF95))[0]


def _hz2mel(hz):
    return 2595.0 * np.log10(1.0 + hz / 700.0)


def _mel_filterbank(nfft=512, nfilter=26, low=80, high=7000, sr=16000):
    feat_width = nfft // 2 + 1
    lowmel = _hz2mel(low)
    highmel = _hz2mel(high)
    nyquist = sr * 0.5
    mel_points = [lowmel + i * (highmel - lowmel) / (nfilter + 1) for i in range(nfilter + 2)]
    bin_mels = [_hz2mel(i * nyquist / (nfft // 2)) for i in range(feat_width)]
    coeff, bank_pos = [], []
    for i in range(nfilter):
        start, stop = -1, -1
        for j in range(1, feat_width):
            lower = (bin_mels[j] - mel_points[i]) / (mel_points[i + 1] - mel_points[i])
            upper = (mel_points[i + 2] - bin_mels[j]) / (mel_points[i + 2] - mel_points[i + 1])
            temp = min(lower, upper)
            if lower > 0 and start == -1:
                start = j
            if upper <= 0 and stop == -1:
                stop = j - 1
            if temp > 0:
                coeff.append(temp)
        bank_pos.append((start, stop))
    return np.asarray(coeff, dtype=np.float32), bank_pos


_HANN = np.hanning(513)[:-1].astype(np.float32)  # periodic 512 hann == reference window array
_COEFF, _BANK_POS = _mel_filterbank()
_OFFS = []
_off = 0
for _s, _e in _BANK_POS:
    _OFFS.append((_s, _e, _off, _off + _e - _s + 1))
    _off += _e - _s + 1


def frame_fbank(samples: np.ndarray) -> np.ndarray:
    """单帧 (512,) int16 -> (26,) log-fbank (Q6.10 值,float)。"""
    x = samples.astype(np.float32) * (1.0 / 32768.0) * _HANN
    spec = np.abs(np.fft.rfft(x, n=512))
    out = np.zeros(26, dtype=np.float32)
    for f, (s, e, c0, c1) in enumerate(_OFFS):
        out[f] = np.dot(spec[s : e + 1], _COEFF[c0:c1])
    return np.log(out + EPS_F32)


class FbankStream:
    """流式 fbank:每帧 512 个 int16 样本 -> 维护 64 帧窗口,输出 (1,26,64)。"""

    def __init__(self, window: int = 64):
        self.window = window
        self.buf = np.zeros((window, 26), dtype=np.float32)

    def push(self, samples: np.ndarray) -> np.ndarray:
        """喂入一帧 int16 (512,),返回当前窗口 (1,26,64) 模型输入。"""
        f = frame_fbank(samples)
        q = np.trunc(np.clip(f, -32, 32) * 1024.0 + 0.5) / 1024.0
        self.buf = np.roll(self.buf, -1, axis=0)
        self.buf[-1] = q
        return self.buf.T[np.newaxis].astype(np.float32)

    def reset(self):
        self.buf[:] = 0.0


def preprocess(*arrays):
    """兼容通用入口:输入 int16 PCM (N,) 或 (1,N),返回全部滑窗 (M,1,26,64)。"""
    if len(arrays) != 1:
        raise ValueError('wakeup 需要单个 int16 PCM 数组')
    pcm = np.asarray(arrays[0]).reshape(-1).astype(np.int16)
    n_frames = (pcm.shape[0] - 512) // 512 + 1
    if n_frames < 1:
        raise ValueError('PCM 太短')
    fs = FbankStream()
    wins = []
    for i in range(n_frames):
        wins.append(fs.push(pcm[i * 512 : i * 512 + 512]))
    return [np.stack(wins)]