谷歌VAD静音检测py-webrtcvad使用详解

一、前言

        不知道有没有人和我一样看的官方示例一脸蒙,什么采样率除以时间,看到就头大,还是英文头更大了

 好了废话结束,上翻译改编版

二、人话翻译版代码

如果你问我有些行为什么没有注释?

-

import collections
import contextlib
import sys
import wave
import os
import webrtcvad

#读取wav判断能否继续
def read_wave(path):
    #以二进制方式堆区音频数据
    with contextlib.closing(wave.open(path, 'rb')) as wf:
        #获取声道数,为1时继续执行
        num_channels = wf.getnchannels()
        assert num_channels == 1
        #采样字节长度,为2时继续执行
        sample_width = wf.getsampwidth()
        assert sample_width == 2
        #获取采样率,当采样率为8000,16000,32000时继续执行
        sample_rate = wf.getframerate()
        assert sample_rate in (8000, 16000, 32000)
        pcm_data = wf.readframes(wf.getnframes())
        #返回wav的二进制数据与采样率
        return pcm_data, sample_rate


def write_wave(path, audio, sample_rate):
    with contextlib.closing(wave.open(path, 'wb')) as wf:
        #设置声道
        wf.setnchannels(1)
        # 设置采样字节长度
        wf.setsampwidth(2)
        # 设置采样率
        wf.setframerate(sample_rate)
        # 写入数据
        wf.writeframes(audio)


class Frame(object):
    """Represents a "frame" of audio data."""

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


def frame_generator(frame_duration_ms, audio, sample_rate):
    #采样率*秒数*声道数=量化大小
    n = int(sample_rate * (frame_duration_ms / 1000.0) * 2)
    offset = 0
    timestamp = 0.0
    #量化大小对应的音频时间
    duration = (float(n) / sample_rate) / 2.0
    #如果offset起始量化大小加上量化大小小于音频 -->用来切分音频
    while offset + n < len(audio):
        #创建多个Frame类,用来存数据   audio[offset:offset + n]切出来的那一段数据,timestamp这段音频开始时间,duration这段音频的长度
        yield Frame(audio[offset:offset + n], timestamp, duration)
        timestamp += duration
        offset += n


def vad_collector(sample_rate, buffer_size, vad, frames):
    # 创建一个有两端的容器,数据溢出用来排除非人声
    ring_buffer = collections.deque(maxlen=buffer_size)

    triggered = False

    voiced_frames = []
    for frame in frames:
        # 把二进制数据和采样率交给vad
        is_speech = vad.is_speech(frame.bytes, sample_rate)
        #检测说话的开始
        if not triggered:
            # 在容器里添加一个对象(Frame类的实例,静音检测结果(True表示有人说))
            ring_buffer.append((frame, is_speech))
            #f = Frame() speech = is_speech 得到有人声音的Frame的个数
            num_voiced = len([f for f, speech in ring_buffer if speech])
            # 当说话段数量大于缓冲区的90%时认为人声开始,所以进入if时前10段有人声恰好在ring_buffer里
            if num_voiced > 0.9 * ring_buffer.maxlen:
                # 通知循环已经找到了音频开始位置
                triggered = True
                #输出当前片段的开始时间
                # 当有9条是人声时,那么最先放进去的一条就是起始位置
                sys.stdout.write('+(%s)' % (ring_buffer[0][0].timestamp,))
                # 把音频的二进制数据放到列表
                for f, s in ring_buffer:
                    voiced_frames.append(f)
                #清空缓存区
                ring_buffer.clear()
        # 检测说话的结束
        else:
            # 上面清空了缓存区,所以现在开始写入有人声的数据
            voiced_frames.append(frame)
            # 把人声的数据写到列表
            ring_buffer.append((frame, is_speech))
            # 获取有多少静音段被写到了ring_buffer
            num_unvoiced = len([f for f, speech in ring_buffer if not speech])
            # 当静音段数量大于90%时执行
            if num_unvoiced > 0.9 * ring_buffer.maxlen:
                # 此时ring_buffer里有10段静音,那么第一段就是人声的结束位置
                sys.stdout.write('-(%s)' % (frame.timestamp + frame.duration))
                # 通知函数找到结束
                triggered = False
                # 暂停当前函数,并返回数据
                yield b''.join([f.bytes for f in voiced_frames])
                # 因为下次调用函数会从yield后面的语句开始所以删除上一段音频数据
                ring_buffer.clear()
                voiced_frames = []
    #如果到音频结束还没有找到结束时,执行到这里,把音频结束当做人声结束, 这里frame自然就是最后一个
    if triggered:
        sys.stdout.write('-(%s)' % (frame.timestamp + frame.duration))
    sys.stdout.write('\n')
    #列表部为空返回所有数据
    if voiced_frames:
        yield b''.join([f.bytes for f in voiced_frames])


def main():
    path = r"./"
    files = os.listdir(path)
    files = [path + "\\" + f for f in files if f.endswith('.wav')]
    con = 1
    for i in range(len(files)):
        args = [3, files[i]]
        #二进制数据,采样率
        audio, sample_rate = read_wave(args[1])
        # 创建VAD实例,并设置模式
        vad = webrtcvad.Vad(int(args[0]))
        #切分数据并返回一堆用来存放数据的Rrame类的生成器
        frames = frame_generator(30, audio, sample_rate)
        frames = list(frames)
        #获取人声段的二进制数据 10决定缓冲区大小
        segments = vad_collector(sample_rate, 10, vad, frames)
        # 给列表加上一个索引(多条音频时使用,segments被)
        for j, segment in enumerate(segments):
            path = './data/' + 'chunk-%002d.wav' % (con,)
            print(' Writing %s' % (path,))
            write_wave(path, segment, sample_rate)
            con += 1


if __name__ == '__main__':
    main()

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值