You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

同一Python脚本中使用VOSK与Resemblyzer致电脑崩溃的优化求助

问题描述

尝试用VOSK实现语音转写、Resemblyzer实现说话人分群,组合完成音频转写+说话人识别功能。单独运行两个工具均正常,但在同一Python脚本中执行时电脑完全冻结,仅IDE界面卡死,需长按电源键强制重启,无蓝屏或黑屏现象。

使用工具

  • VOSK
  • Resemblyzer

电脑配置

Intel(R) Core(TM) i3-7100 CPU @ 3.90GHz,3912MHz,2核4线程,32GB内存

问题代码

from vosk import Model, KaldiRecognizer
from pydub import AudioSegment
import json
import sys
import os
import subprocess
import datetime
from resemblyzer import preprocess_wav, VoiceEncoder
from pathlib import Path
from resemblyzer.hparams import sampling_rate
from spectralcluster import SpectralClusterer
import threading
import queue
import gc



def recognition(queue, audio, FRAME_RATE):

    model = Model("Vosk_Models/vosk-model-small-en-us-0.15")

    rec = KaldiRecognizer(model, FRAME_RATE)
    rec.SetWords(True)

    rec.AcceptWaveform(audio.raw_data)
    result = rec.Result()

    transcript = json.loads(result)#["text"]

    #return transcript
    queue.put(transcript)



def diarization(queue, audio):

    wav = preprocess_wav(audio)
    encoder = VoiceEncoder("cpu")
    _, cont_embeds, wav_splits = encoder.embed_utterance(wav, return_partials=True, rate=16)
    print(cont_embeds.shape)

    clusterer = SpectralClusterer(
        min_clusters=2,
        max_clusters=100,
        p_percentile=0.90,
        gaussian_blur_sigma=1)

    labels = clusterer.predict(cont_embeds)

    def create_labelling(labels, wav_splits):

        times = [((s.start + s.stop) / 2) / sampling_rate for s in wav_splits]
        labelling = []
        start_time = 0

        for i, time in enumerate(times):
            if i > 0 and labels[i] != labels[i - 1]:
                temp = [str(labels[i - 1]), start_time, time]
                labelling.append(tuple(temp))
                start_time = time
            if i == len(times) - 1:
                temp = [str(labels[i]), start_time, time]
                labelling.append(tuple(temp))

        return labelling

    #return
    labelling = create_labelling(labels, wav_splits)
    queue.put(labelling)



def identify_speaker(queue1, queue2):

    transcript = queue1.get()
    labelling = queue2.get()

    for speaker in labelling:

        speakerID = speaker[0]
        speakerStart = speaker[1]
        speakerEnd = speaker[2]

        result = transcript['result']
        words = [r['word'] for r in result if speakerStart < r['start'] < speakerEnd]
        #return
        print("Speaker",speakerID,":",' '.join(words), "\n")




def main():

    queue1 = queue.Queue()
    queue2 = queue.Queue()

    FRAME_RATE = 16000
    CHANNELS = 1

    podcast = AudioSegment.from_mp3("Podcast_Audio/Film-Release-Clip.mp3")
    podcast = podcast.set_channels(CHANNELS)
    podcast = podcast.set_frame_rate(FRAME_RATE)

    first_thread = threading.Thread(target=recognition, args=(queue1, podcast, FRAME_RATE))
    second_thread = threading.Thread(target=diarization, args=(queue2, podcast))
    third_thread = threading.Thread(target=identify_speaker, args=(queue1, queue2))

    first_thread.start()
    first_thread.join()
    gc.collect()

    second_thread.start()
    second_thread.join()
    gc.collect()

    third_thread.start()
    third_thread.join()
    gc.collect()

    # transcript = recognition(podcast,FRAME_RATE)
    #
    # labelling = diarization(podcast)
    #
    # print(identify_speaker(transcript, labelling))


if __name__ == '__main__':
    main()

优化方案与问题修复

核心问题分析

  1. 线程逻辑冗余:代码创建线程后通过join()强制串行执行,既没用到多线程并行优势,还增加了线程调度开销。
  2. 内存占用过高:AudioSegment对象重复传递可能导致多份音频数据副本;模型在函数内重复初始化,浪费内存。
  3. 音频处理不合理:一次性传入全部音频数据到VOSK,容易造成内存峰值。

优化后的代码

from vosk import Model, KaldiRecognizer
from pydub import AudioSegment
import json
from resemblyzer import preprocess_wav, VoiceEncoder
from resemblyzer.hparams import sampling_rate
from spectralcluster import SpectralClusterer
import gc

# 全局初始化模型,避免重复加载
vosk_model = Model("Vosk_Models/vosk-model-small-en-us-0.15")
voice_encoder = VoiceEncoder("cpu")

def recognition(audio, frame_rate):
    rec = KaldiRecognizer(vosk_model, frame_rate)
    rec.SetWords(True)
    
    # 分块处理音频,降低内存峰值
    chunk_size = 4000
    for i in range(0, len(audio), chunk_size):
        chunk = audio[i:i+chunk_size]
        rec.AcceptWaveform(chunk.raw_data)
    
    result = rec.Result()
    return json.loads(result)

def diarization(audio):
    wav = preprocess_wav(audio)
    _, cont_embeds, wav_splits = voice_encoder.embed_utterance(wav, return_partials=True, rate=16)
    
    clusterer = SpectralClusterer(
        min_clusters=2,
        max_clusters=100,
        p_percentile=0.90,
        gaussian_blur_sigma=1)
    
    labels = clusterer.predict(cont_embeds)
    
    def create_labelling(labels, wav_splits):
        times = [((s.start + s.stop) / 2) / sampling_rate for s in wav_splits]
        labelling = []
        start_time = 0
        
        for i, time in enumerate(times):
            if i > 0 and labels[i] != labels[i - 1]:
                labelling.append((str(labels[i-1]), start_time, time))
                start_time = time
            if i == len(times) - 1:
                labelling.append((str(labels[i]), start_time, time))
        
        return labelling
    
    return create_labelling(labels, wav_splits)

def identify_speaker(transcript, labelling):
    for speaker_id, start, end in labelling:
        words = [r['word'] for r in transcript['result'] if start < r['start'] < end]
        print(f"Speaker {speaker_id}: {' '.join(words)}\n")

def main():
    FRAME_RATE = 16000
    CHANNELS = 1

    # 仅加载一次音频并预处理
    podcast = AudioSegment.from_mp3("Podcast_Audio/Film-Release-Clip.mp3")
    podcast = podcast.set_channels(CHANNELS).set_frame_rate(FRAME_RATE)
    
    # 串行执行适配2核CPU,避免资源耗尽
    transcript = recognition(podcast, FRAME_RATE)
    gc.collect()  # 主动释放识别阶段临时内存
    
    labelling = diarization(podcast)
    gc.collect()  # 主动释放分群阶段临时内存
    
    identify_speaker(transcript, labelling)

if __name__ == '__main__':
    main()

关键优化点说明

  1. 全局模型初始化:VOSK和Resemblyzer模型只加载一次,减少内存占用和重复初始化时间。
  2. 音频分块处理:VOSK识别时分块传入音频,降低内存峰值。
  3. 移除冗余线程:针对2核CPU,串行执行避免资源竞争,减少线程调度开销。
  4. 主动内存回收:完成阶段性任务后调用gc.collect(),及时释放无用内存。
  5. 代码简化:去掉不必要的队列和线程,逻辑更清晰,减少潜在同步问题。

额外建议

  • 若音频文件过大,可切割为小片段分批处理,进一步降低内存压力。
  • 监控CPU和内存占用,排查是否存在内存泄漏或过载情况。
  • 确认VOSK模型路径正确,避免加载时的隐性错误。

内容的提问来源于stack exchange,提问作者SL1997

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 15:05:33